Архив рубрики: Основы объектно-ориентированного программирования

Контейнеры STL: vector. Часть 3




контейнеры STL C++ , Standard Template Library, вектор с++, vector c++

В предыдущем уроке мы определили переменную типа vector<float>, как эквивалент массива, размер которого мы сможем произвольно изменять по ходу выполнения кода. Но это не означает (так же как и для классических векторов C++), что мы можем таким образом создавать только динамические массивы простейших встроенных типов. Тип элемента вектора может быть произвольным и сколь угодно сложным! Например, мы могли бы описать студенческую группу так:

контейнеры STL C++ , Standard Template Library, вектор с++, vector c++

В этом примере, помимо прочего, мы применили указатель this и перегрузку операторов. Оставляю ссылки для вас, если кому-то необходимо освежить это в памяти.

Шаблонным классом вектора (и любого контейнера STL) может быть, в свою очередь, контейнер STL. Например vector< vector<int> > или vector< vector< vector<int> > > (не забудьте пробел между закрывающимися скобками ‘>‘ — это особенность синтаксического разборщика). Таким образом мы можем, например, создать класс треугольных матриц:

контейнеры STL C++ , Standard Template Library, вектор с++, vector c++
Следующим уровнем нашего углубления в технику векторов, и контейнеров STL вообще, будет понятие итератора. Итератор — центральное понятие для работы с контейнерами STL. Итератор — это некоторая абстракция, которая применяется для выполнения итерации (перебора) элементов в контейнере STL и предоставления доступа к отдельным элементам. Итератор p не является указателем, но, на первых порах, вы можете условно считать его как нечто подобное по виду: *p будет обозначать значение данных под текущим итератором, p++ переводит итератор на следующий элемент контейнера, а p— (когда это допустимо) — на предыдущий элемент. Для разных типов контейнеров, соответствующие им итераторы могут относиться к одной из 5-ти категорий: входные, выходные, однонаправленные, двунаправленные и произвольного доступа. Итераторы векторов — это итераторы прямого доступа. Именно поэтому для векторов возможна операция индексации. Этих, достаточно поверхностных, знаний про итераторы нам достаточно для того, чтобы начать работать с ними.

Воспроизведём в терминах итераторов задачу нахождения всех простых чисел, не превосходящих N (решето Эратосфена), которую мы уже решали раньше в технике массивов C++:

Как легко видеть из описания vector<bool>::iterator, что итератор хранит в себе вид контейнера, к которому относится, и тип элементов этого контейнера. Это требует достаточно громоздкой записи с точным описанием типа итератора. Но последний стандарт C++11 ввёл понятие выводимости типа: если требуемый тип объекта выводится из контекста его использования, то тип объекта может быть объявлен описателем auto (выводимый тип). Тогда строка 16 показанного выше кода может быть записана так:

Наконец, для векторов (и для всех контейнеров, имеющих двунаправленные итераторы, как упоминалось выше) могут быть определены реверсные итераторы, которые перемещаются не от начала контейнера к концу, а наоборот — с конца в начало. Такой итератор должен объявляться как совсем другой тип, например:

Но и здесь мы можем положиться на выведение типов, как в следующем примере:

контейнеры STL C++ , Standard Template Library, вектор с++, vector c++

Все возникающие вопросы по уроку задавайте в комментариях.

Рассылка новых уроков по программированию:

Задача: Класс «Геометрическая прогрессия»

Классы С++ могут создаваться для описания самых неожиданных сущностей реального мира. Создайте класс «геометрическая прогрессия», объекты которого:

  • отображали бы геометрическую прогрессию с заданным начальным членом и знаменателем;
  • по индексации позволяли получить значения члена прогрессии с любым порядковым номером;
  • позволяли получить сумму начальных N-членов прогрессии;

Дополните класс, возможно, другими полезными свойствами.

Решение:

Можно было бы, конечно, в качестве класса геометрической прогрессии хранить нужное число начальных членов ряда. Но это а). крайне расточительно и б). при таком представлении непонятно, какое число членов ряда хранить? Показанное же решение компактное и эффективное:

Особенностью решения является то, что при выполнении операции индексации (извлечения члена прогрессии) делается быстрое рекурсивное (алгоритм Хоара) возведение в степень знаменателя.

Параметром командной строки можно переопределить длину тестового массива, куда переписываются члены прогрессии:

Рассылка новых уроков по программированию:

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




конструктор копирования в с++,  конструктор копии c++, программирование на с++ для начинающихКогда новички изучают программирование, первым делом, при рассмотрении новой темы, возникает вопрос — для чего необходима та или иная «вещь» о которой сейчас предстоит узнать. Ответим сразу на этот вопрос: «Зачем нужен конструктор копирования?».

Конструктор копирования необходим для того, чтобы мы могли создавать «реальные» (а не побитовые) копии для объектов класса. Такая копия объекта может понадобиться в следующих случаях:

  • при передаче объекта класса в функцию, как параметра по значению (а не по ссылке);
  • при возвращении из функции объекта класса, как результата её работы;
  • при инициализации одного объекта класса другим объектом этого класса.

При передаче объекта в функцию как параметра по значению, эта функция начнет работать с его побитовой копией, а не с полями самого объекта. Допустим определены конструктор и деструктор класса. Первый память выделяет, а второй её освобождает. Во время работы функции, указатель побитовой копии объекта указывает на адрес памяти, где расположен оригинальный объект. В то время, когда работа функции завершается — удаляется и побитовая копия объекта. При ее удалении обязательно сработает определённый деструктор и освободит ту память, что занята объектом-оригиналом. Программа продолжит работу, и при завершении работы, деструктор сработает повторно, пытаясь освободить все тот же отрезок памяти. Это вызовет ошибку программы.

Использование конструктора копирования — прекрасный способ обойти эти ошибки и проблемы. Он создаст «реальную» копию объекта, которая будет иметь личную область динамической памяти.

Конструктор копирования синтаксически выглядит так:

Ниже разберём несложный, но очень показательный пример. В нём будут рассмотрены все 3 случая в которых желательно применять конструктор копирования. Будет создан класс, содержащий конструктор без параметров, конструктор копирования и деструктор. Чтобы пример был не слишком громоздким, конструкторы и деструктор будут выводить на экран сообщения типа «Сработал конструктор», «Сработал дектруктор»… Выделять и освобождать память не будем. Нам отлично будет видно сколько раз сработают конструкторы а сколько раз деструктор. Очевидно, что деструктор (если бы он освобождал память) не должен срабатывать большее количество раз, чем конструктор, выделяющий память.

Пример:

Конструктор без параметров будет вызываться во время создания новых объектов класса. Конструктор копирования — во время создания копий объекта. Деструктор срабатывает при удалении и реального объекта и его копии. В теле функций все описано подробно и не требует дополнительных комментариев.

Запустив программу увидим в консоли следующее:

конструктор копирования в с++,  конструктор копии c++,  программирование на с++ с нуля

Посмотрим что программа выдала в консоль. Блок 1 — во время создания нового объекта, сработал конструктор без параметров. В блоке 2 мы разместили функцию showFunc(). Во время передачи в неё «объекта-параметра» по значению, сработал конструктор копирования и создалась «реальная» копия объекта класса OneClass. При выходе из этой функции сработал деструктор, так как копия объекта уничтожается. Кстати, то, что передача объекта как параметра по значению, вызывает конструктор копирования, служит отличным поводом для передачи объекта по ссылке. Это сэкономит и время и память.

В блоке 3 размещена функция returnObjectFunc(). Так как в её теле прописано создание нового объекта класса OneClass — сначала сработал конструктор без параметров. Далее выполняется код функции и во время возврата объекта в главную функцию main, сработал конструктор копирования. В конце, как и должно быть, деструктор отработал дважды: для объекта и для его реальной копии.

В четвертом блоке, во время объявления и инициализации нового объекта object2, сработал конструктор копирования. При завершении работы программы деструктор сработал для копии объекта из четвертого блока и для объекта object1 из первого блока.

Если же мы закомментируем /*конструктор копирования*/ в классе и снова запустим программу — увидим, что конструктор без параметров сработает 2 раза, а деструктор — пять раз отработает.

конструктор копирования в с++,  конструктор копии c++, программирование на с++ с нуля

В этой ситуации, если бы деструктор освобождал память — в программе возникла бы ошибка.

Очень рекомендую прочесть тему Конструктор копирования в книге Стивена Прата «Язык программирования С++. Лекции и упражнения. 6-е издание.» Она раскрыта намного глубже и включает все основные нюансы использования конструктора копирования. Подробно рассмотрена операция присваивания =.

Чтобы поддержать наш сайт — нажмите на копилку и выберите любой удобный способ.

Рассылка новых уроков по программированию:

Список двунаправленный. Сортировка по полям и условиям




В этой статье я опишу методику работы с двунаправленным списком в классическом его варианте. Ввод списка, вывод и сортировку по разнообразным условиям и полям элементов списка. Не буду вдаваться в теорию списков — перейду к описанию того, что необходимо сделать для решения данной задачи, и по ходу дела опишу почему именно так я организовал работу.

Для построения списка нам понадобятся две структуры: Одна — поля элемента списка. Вторая — сам элемент списка с якорями, которые будут связывать элементы между собой.

Первая структура может выглядеть так:

Это структура данных списка. В ней должны содержаться поля, которые непосредственно к самому списку (вернее к его структуре ) отношения не имеют, зато хранят информацию. Эта запись — контейнер (назовем ее так).

Вторая запись:

Она уже призвана хранить в себе элемент контейнера, в который мы упакуем данные, и поля для связи с элементами-соседями в списке.

Для чего понадобился такой разворот на две структуры? Так будет удобнее производить сортировку. В обычных условиях список сортируется путем перенаправления его полей-якорей на другие элементы (Next и Prev в данном примере). Т.е. сами данные, как были в памяти так в той же ячейке (ячейках) и остаются, а меняются только указатели на соседей. И это конечно хорошо и правильно, но сложно. Чем выше сложность, тем больше вероятность нарваться на баг в программе. Поэтому стоит упростить программу, чтобы можно было не якоря менять, а данные местами (как обычно это делается в сортировке массивов к примеру). Поэтому целесообразнее данные выделить в отдельный блок-структуру, чтоб перетягивать их одним оператором, а не таскать каждое поле отдельно. Ниже вы увидите что имеется ввиду.

Для работы со списком нужны переменные головы списка и, по необходимости, хвоста:

И вот так уже будет выглядеть процедура наполнения списка:

Ее тоже можно было разделить на две: Первая создавала бы элемент, вторая наполняла бы его контейнер полезными данными, но это уже по вкусу.

Не забываем о процедуре освобождения списка. Оставлять мусор в памяти — дурной тон, даже если умная операционка сама умеет чиститься:

Процедура вывода:

Такая большая она из-за «марафета». Красивый вывод на экран — достаточно важная опциональность программы. Поэтому ширину для элементов и табличный вид стоит соблюсти.

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

В нее договоримся передавать опции сортировки: Имя поля, которое подлежит сортировке, и направление сортировки (asc — по возрастанию или desc — по убыванию).

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

Функция анализатор выглядит так:

Она выполняет 4 условия:

  1. Если asc — по возрастанию, и сортируемое поле — х:
  2. Если desc — по убыванию, и поле то же
  3. Если asc — по возрастанию, но сортируется второе поле, строковое
  4. Если desc — по убыванию с строковым полем

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




Собираем описанное выше в одну программу и дописываем функцию main():

Обратите внимание: Я словами указываю, как сортировать список, по какому полю и в каком порядке.

Результат:

сортировка двунаправленного списка в С++

О втором методе сортировки списка путем перезацепления якорей можете почитать на programmersforum

По просьбе коллег по сайту (которые конечно же правильно заметили) стоит хотя бы вкратце упомянуть о прелестях C++ и STL. Имею ввиду класс list, который собственно олицетворяет списки (двунаправленные). Посыл простой: Все, что нужно для работы со списком уже сделали. По хорошему, программисту, работающему со списками, конечно же стоит в бытовых случаях избирать для работы STL.

Опишу небольшой код с применением этого контейнера, как альтернатива тому, что описано выше:

сортировка двунаправленного списка в С++

Если задача позволяет использовать STL — используйте. Решение окажется надежнее и не будет траты времени на изобретения своего механизма хранения списков.

Выбор из этих двух методов стоит делать в зависимости от количества полезных данных, что вводятся в элемент списка. Если это пара десятка полей с числами или небольшими строками, то самый простой способ, как раз — перемещение контейнера с полезными данными (как здесь). Если же элементы списка имеют огромный размер, то быстрее будет выполняться методика перестановки якорей (изменения Last и Next для перезацепления элемента к другому элементу). В любом случае выбор за программистом.

Чтобы поддержать наш сайт — нажмите на копилку и выберите любой удобный способ.

Рассылка новых уроков по программированию:

Перегруженный конструктор класса




перегруженный конструктор класса с++, основы программирования для начинающих, c++ с нуля

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

Рассмотрим пример:

перегруженный конструктор класса с++, основы программирования для начинающих, c++ с нуля На этом примере достаточно легко увидеть, чем вызвана необходимость перегрузки конструкторов. Основной смысл в том, чтобы дать возможность программисту выбрать наиболее подходящий способ инициализации объекта.

Тут представлен самый распространенный вариант перегрузки конструкторов. А именно конструктор с параметрами и второй без параметров. Часто, программисту бывают необходимы оба подобных конструктора, так как конструктор с параметрами удобно использовать, работая с одиночными объектами. Но он не может быть использован, например, для инициализации динамического массива объектов класса.

При каждом объявлении объекта класса надо применять соответствующий определённым конструкторам способ объявления.

Конструктор можно перегружать столько раз, сколько посчитаете нужным. Но, как бывает у большинстве случаев, желательно придерживаться золотой середины. Перегружайте его только для наиболее распространенных ситуаций.

Хочется добавить, что деструктор, в отличии от конструктора, перегружен быть не может, так как он никаких параметров не принимает.

Чтобы поддержать наш сайт — нажмите на копилку и выберите любой удобный способ.

Рассылка новых уроков по программированию:

Класс String в C++




класс string с++, программирование для начинающих

Использование текстовой информации во время работы с базами данных, файлами, для получения и отправления сообщений, является неотъемлемой частью работы каждого программиста. Строки применяются, как для внутренних целей (отладки и тестирования кода), так и для решения общих задач, передачи данных клиенту, оформления графического интерфейса. Ввиду этого следует уделить особое внимание тому, как устроены строки в языке С++, как работать с их базовыми операции и какие методы обработки объектов типа string быстрее, либо целесообразнее в той или иной ситуации.

Строки в языке C++ могут представлять собой массивы символов char — это встроенный тип, который применялся в С. Либо экземпляры класса string, который включен в стандартную библиотеку С++. Следует заметить, что при создании объекта данного типа мы практически получаем динамически изменяющийся массив, т.е. тот, которому нет необходимости задавать начальные размеры, поскольку они могут меняться в процессе работы с сущностью.

В первом случае строка размещается в памяти, как массив. Через указатель типа char осуществляется доступ к ней. Данный подход чреват ошибками и является довольно сложным, поскольку реализован на низком уровне. Объекты класса string облегчают работу, через них можно получить доступ к стандартным операциям над строками. Они же входят в пространство имен stl (Standard Template Library). Для дальнейшего использования стандартных библиотек С необходимо подключить заголовочный файл:

Простейшим примером использования переменной строчного типа будет выведение её на экран. Но усложним задачу и попробуем, для начала, считать значение строки, а после её оперировать.

Метод getline() позволяет считывать данные из потока cin, а также записывать их в переменную, которая указывается после запятой.

В случае, когда необходимо инициализировать переменную во время ее создания, следует использовать следующий синтаксис:

Рассмотрим базовые методы класса string, которые позволяют модифицировать строки и получать информацию о их параметрах.

Для получения длины строки можно применять два метода. Один из них size() другой length(). Оба метода возвращают численное количество символов. Но следует заметить, что нумерация элементов в строковом массиве все равно начинается с 0.

Листинг для проверки:

Далее мы видим, что оба метода возвращают одинаковые значения.

работа со строками в C++, класс String C++, методы length() и size()

Для проверки строковой переменной на наличие данных в ней используется функция empty(), которая возвращает булевое значение ( 0 если false или 1 если true). Данная операция полезна, когда необходимо себя обезопасить от потери данных.
Чтобы обнулить строчную переменную применяется метод clear(). В результате после его выполнения myString.size() вернет 0, а myString.empty() — значение true, т.е. 1.
Листинг кода для проверки функций:

Результаты выполнения:
работа со строками в C++, класс String C++

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

Функция push_back (char) позволяет добавить в конец текущей строки любой символ типа char. При этом, нам нет необходимости пересохранять новое значение строки, старая переменная просто будет модифицированной.

Класс string позволяет манипулировать двумя строками одновременно, записывая значения из одной в другую. Для этого на объекте строки следует вызвать метод insert (int startInResultString, string from, int startInSourceString, int amount). Где startInResultString — стартовая позиция ячейки в строке, в которую производится запись новых данных, т.е. новые значения будут размещаться начиная с этого индекса. Строчная переменная from представляет собой строку из которой берутся данные, далее все параметры относятся именно к ней. Начальное значение индекса с которого начнется копирование данных обозначено startInSourceString. Общее количество символов, которые будут скопированы из строки from обозначено amount, отсчет производится начиная со стартового индекса.
Листинг кода позволит лучше разобраться со входными параметрами функции.

Результаты выполнения кода:

работа со строками в C++, класс String C++

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

Экспериментируйте — создайте объект класса string и вызывайте его методы по-очереди. Старайтесь сами разобраться, что они делают и как работают. Во многих учебниках предлагается самостоятельно написать свой собственный (конечно же укрощенный) класс для работы со строками. Это совсем несложно и вы отлично разберетесь с тем, как устроен настоящий класс string. Мы подготовим для вас несколько задач, для закрепления этого материала.

Обязательно посмотрите видео по теме класс (автор Денис Марков):

Рассылка новых уроков по программированию:

Указатели на объекты




с++, указатели на объекты, программирование для начинающих, c++При первом знакомстве с указателями в C++ (см. Указатели в C++. Часть 1 ) может сложиться упрощённое представление, что указатели могут указывать только на отдельные переменные встроенных (скалярных) типов C++, и что это просто ещё одна, альтернативная форма доступа к таким переменным. В таком применении указатели были бы приятным дополнением языка, но с весьма ограниченными возможностями.

При более внимательном изучении указателей C++ мы обнаруживаем, что указатель может быть адресом размещения (указывать на) любого допустимого объекта в программе: структуры, объекта класса, массива, функции, или опять же указателя на некоторый объект, или указателя на указатель и так далее… Это делает указатели чуть ли не самым мощным инструментом программиста на C++ … но и самым опасным в смысле возможных скрытых ошибок.

Рассмотрим такие варианты подробнее. Самым простым вариантом будет использование указателей на составные объекты (объекты классов и структуры). Но уже такое использование указателей открывает широкие перспективы в C++, как мы сейчас увидим.

Итак, мы уже знаем, что описание класса не создаёт никаких новых объектов данных в программе — это только описание некоторого шаблона, нового типа данных, согласно которому будут создаваться реальные объекты данных. При создании (объявлении) новых объектов данных мы можем вычислять адрес этих объектов и присваивать их указателям на объекты этого класса. Напишем простейшую программу, оперирующую с указателями на объекты некоторого класса (файл ex1.cc):

Здесь мы видим относительно новую в наших темах конструкцию:

Это конструктор нового класса my, но с параметром создания. При вызове он вызывает конструктор родительского класса (number(numb)), передавая ему это же значение параметра. Следующие далее скобки {} обрамляют пустой блок кода, который означает, что ничего более сверх вызова родительского конструктора делать не нужно. Вспоминаем, что вся последовательность конструкторов всех родительских классов — вызывается (в порядке обратном наследованию) при вызове конструктора порождённого класса, но это только для конструкторов без параметров. В случае параметризированных конструкторов родиелей вам прийдётся вызывать явно.

Но мы на этом отвлеклись в сторону от предмета нашего изложения… А теперь самое время компилировать и посмотреть выполнение полученной нами программы:

с++, указатели на объекты, программирование для начинающих, c++

Пока ничего принципиально нового, и всё это сильно похоже на то, как мы работали бы с указателями на переменные типа double, скажем.

Вспомним, в дополнение, что оператор new для динамического создания нового объекта:

а) вызывает менеджер динамического управления памяти и выделяет новый объем под размещение такого объекта;

б) вызывает конструктор соответствующего класса (типа данных) для начальной разметки (инициализации) выделенной памяти. Слегка модифицируем свой пример (файл ex2.cc):

Сборка и выполнение:

с++, указатели на объекты, программирование для начинающих, c++

Пока ещё не произошло никаких радикальных изменений …, но обратим внимание на то, что в программе (а это могла бы быть сколь угодно большая программа) вообще не объявлены и не используются в явном виде объекты — программа оперирует только указателями на объекты.

И, наконец, мы приближаемся к той технике в использовании указателей на объекты, которая делает указатели самым мощным инструментом работы с классами и их объектами в C++. А именно: виртуализация функций-членов класса и полиморфизм. Понятие полиморфизма (изменчивости формы, многоликости) — одно из основных направлений развития C++ от его предшественника языка C. Оно состоит в том, что прародитель целого семейства наследуемых классов объявляет некоторые свои функции-методы как virtual. А в разных наследуемых классах эти функции-методы переопределяются по-разному. Это означает, что в различающихся наследуемых классах (родственных) функция-метод с одним и тем же именем будет выполняться несколько различающимся способом, в зависимости от конкретного класса в котором она используется.

Продемонстрируем эту технику на примере. Создадим (файл ex3.cc) класс, описывающий вообще любые плоские фигуры на плоскости (это могут быть элементы некоторого 2D игрового сценария):

Здесь перед нами образец того, что в C++ называется абстрактный класс: класс, в котором определена хотя бы одна виртуальная функция с определением вида:

В показанном классе таких функций аж 3. Естественно, что создать объект абстрактного класса невозможно, в нём есть функции, тело которых не определено. Попытка объявления объектов такого класса вызовет синтаксическую ошибку. Но от такого класса можно наследовать, создавать производные классы, которые унаследуют общие свойства родового абстрактного (например, координаты x и y центра фигуры и её размер r).

Определим 3 производных от figure класса: круг, квадрат и равносторонний треугольник:

Теперь мы готовы создать программу, для которой строились все эти приготовления: создать произвольное число различных 2D геометрических объектов, над которыми можем выполнять единообразные (виртуальные) действия, не взирая на их различную природу:

На этом простейшем примере показано то, что в объектной модели языка C++ называется полиморфизм. И это свойство является одним из самых мощных выразительных инструментов языка C++. И реализуется эта техника всегда через указатели на объекты (figure*).

Вот как будет выглядеть компиляция и выполнение нашего примера (ex3.cc) в терминале операционной системы Linux при использовании GCC компилятора с языка C++ (это будет лишний раз подтверждением того, что программирование на языке C++ в меньшей мере зависит от операционной системы):

с++, указатели на объекты, программирование для начинающих, c++

Ещё раз обратимся к коду показанного примера, и лишний раз зафиксируем то чрезвычайно важное обстоятельство, что указатели C++ всегда типизированы: указатель не может быть «указателем на что-то». Язык C++ — это язык со строгой именной типизацией. Типом же указателя является: указатель на тип указываемой ним переменной, например «указатель на double». Указатели на различные типы несовместимы между собой по присвоению и сравнению.

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

Чтобы поддержать наш сайт — нажмите на копилку и выберите любой удобный способ.

Рассылка новых уроков по программированию:

Перегрузка операторов в С++





перегрузка операторов c++, перегрузка операторов с++, перегрузка операций с++, перегрузка операций c++

Перегрузка операторов в какой-то степени творческий процесс. С её помощью, у программистов появляется возможность облегчить для себя написание кода, а для других — повысить читабельность. Например, когда какое-то действие приходится повторять в коде много раз, и просто мучительно постоянно использовать для этого специальные функции — можно перегрузить оператор для этого. Допустим, в коде надо часто объединять строки (дописывать строки в строку элемент класса). Это можно оформить по-разному. Но мы сделаем так, чтобы объединение строк происходило тогда, когда мы используем оператор + :

Результатом этого должно стать изменение элемента str. Он вместит в себе обе строки: «Название нашего сайта PureCodeCpp».

Пока мы явно не перегрузим оператор +, компилятор будет «ругаться», так как мы складываем не числа, а строки. Но сейчас мы научимся указывать ему, как надо действовать, когда мы просим выполнить нестандартное действие, используя +.

Приступаем к практике. Определим в примере 4 строки. Далее соберем из них анекдот, объединив их в правильном порядке в одну строку.

Не будем говорить об очевидном — перейдем к разбору самой перегрузки. В строке 15 мы видим прототип метода класса Overload: void operator +(char*); Метод этот заставит работать оператор + так, как мы того захотим (так как мы определим ниже). Чтобы перегрузить какой-либо оператор нужно использовать ключевое слово operator. В нашем случае, метод не возвращает значений поэтому void, далее ключевое слово operator и сам оператор +. Принимает этот метод указатель на строку.

В строках 20 — 23 располагается определение метода перегрузки оператора. (Как определить метод вне класса читайте в статье Классы в С++) В нем, используя функцию strcat_s(str, s); производится запись строки s в конец строки str (элемент класса). Действует это так — как только в коде встретится оператор + за которым будет располагаться строка — будет вызван метод перегрузки оператора и эта строка передастся в него по указателю.

В главной функции у нас определены в случайном порядке 4 строки. Отображаем их на экране (строки 39 — 42). Ниже, в строке 44, объявлен объект Joke. Во время его создания, конструктор класса, очистит элемент класса str от «мусора» и он будет готов для записи строк. Осталось выполнить простые действия (строки 46 — 49) — используя перегруженный + записать все строки в одну (str) в правильном порядке.

Результат:

перегрузка операторов c++,  перегрузка операций c++

Все получилось. Еще, как видно в результате, для числовых данных оператор + сработал правильно. Поэтому можно спокойно применять его для арифметических операций в коде — компилятор вас «поймет». Еще один момент — перегрузка оператора действует только в пределах того класса, для которого она определена. Если мы определим еще один класс (Overload2 например) но не перегрузим для него оператор, то попытка использовать + для записи строки куда-либо приведет к ошибке.




Есть ряд исключений в С++ — не все операторы можно перегрузить. Вот перечень:

перегрузка операторов c++,  перегрузка операций c++

И еще немного теории:

— перегрузка операторов не может изменить приоритет и порядок выполнения операций;

— нет возможности, с помощью перегрузки, создать новые символы для операций;

— бинарные операторы не могут быть использованы для переопределения унарной операции и наоборот — унарный оператор не переопределит бинарную операцию.

Перегрузка операторов, конечно, «вещь» интересная. Только не стоит увлекаться. Используйте её только по мере острой необходимости — если это действительно будет приносить больше удобства, экономить вам время и положительно скажется на читабельности кода. Старайтесь перегружать операторы так, чтобы это было как можно ближе к их логическому значению. То есть не надо складывать строки, перегружая оператор , например. Логичнее использовать +. Отмечу, что многие программисты не очень любят перегрузку операторов, так как некоторые чрезмерно ней увлекаются и читать код становится сложно. Так что внимательно взвешивайте все за и против, принимая решение о перегрузке операторов.

Я планирую в будущем написать еще одну статью о перегрузке операторов, где на примерах хочу показать как перегрузить ++ инкремент, декремент, == равенство, = присваивание new и delete. Хоть бы не забыть :)

Есть видео по теме. Автор MyNekis.

Чтобы поддержать наш сайт — нажмите на копилку и выберите любой удобный способ.

Рассылка новых уроков по программированию:

Указатель this С++





указатель this в c++, this c++, this с++

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

Когда будете рассматривать эти примеры, обратите внимание на то, как функции из первого примера и методы класса из второго примера, будут принимать параметры.

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

Рассмотрев этот пример, вы увидели, что определяя методы в теле класса, мы не прописываем параметры в сигнатуре. И вызывая эти методы из главной функции, мы так же не указываем с какими данными им работать. Но каким-то образом данные вносятся именно в те члены класса, которые указаны в теле методов. Как же методы «понимают», с какими данными и с каким объектом класса им надо работать? Дело в том, что в методы класса, неявно передается в виде параметра указатель this (указатель на объект класса). Происходит это автоматически. Мы этого не видим, так как этот указатель — есть скрытый первый параметр любого метода класса.

Указатель this хранит адрес определённого объекта класса. В рассмотренном примере он хранит адрес объекта objectOfClass. Таким образом он неявно указывает методам класса с данными какого объекта надо работать.




Отмечу, что у программистов все же есть возможность применять указатель this явно. Если бы мы определяли метод enterData() с явным использованием this, это выглядело бы так:

указатель this в c++, this c++, this с++

Или так:

указатель this в c++, this c++, this с++

Конкретно в этих случаях можно обойтись и без явного использования this. Но иногда явного использования не избежать. К примеру в следующем примере, this не позволит компилятору запутаться в именах членов класса и параметров которые принимает конструктор.

В строке 10 мы определили конструктор с параметрами, имена которых совпадают с именами членов класса: someName и someAge. Далее чтобы дать компилятору понять что именно и куда надо скопировать, мы явно используем указатель this:

Это означает, что в член класса someAge необходимо записать значение, которое будет задано про создании объекта класса.

указатель this в c++, this c++, this с++

Подведем итог : Указатель this — это указатель, который хранит адрес конкретного объекта класса. Он присутствует в виде скрытого первого параметра в каждом методе класса (кроме статических методов). Типом этого указателя является имя класса. В методах класса, при необходимости, можно использовать this явно. Однако явно объявлять, инициализировать или изменять этот указатель, нет возможности.

Надеюсь боле-менее вы разобрались с этой темой. Если есть вопросы — пишите в комментариях.

Чтобы поддержать наш сайт — нажмите на копилку и выберите любой удобный способ.

Рассылка новых уроков по программированию:

Конструктор и деструктор класса в C++




конструктор класса c++, деструктор класса c++, конструктор и деструктор класса с++
Возможно вы заметили, что определяя класс, мы не можем инициализировать его поля (члены) в самом определении. Можно присвоить им значение, написав соответствующий метод класса и вызвав его, после создания объекта вне класса. Такой способ не совсем удобен, так как объявляя, допустим, 33 объекта класса нам придется 33 раза вызывать метод, который присваивает значения полям класса. Поэтому, как правило, для инициализации полей класса, а так же для выделения динамической памяти, используется конструктор.

Конструктор (от construct – создавать) — это особый метод класса, который выполняется автоматически в момент создания объекта класса. То есть, если мы пропишем в нем, какими значениями надо инициализировать поля во время объявления объекта класса, он сработает без «особого приглашения». Его не надо специально вызывать, как обычный метод класса.

Пример:

В строках 11 — 17 определяем конструктор: имя должно быть идентично имени класса; конструктор НЕ имеет типа возвращаемого значения (void в том числе). Один объект объявляется сразу во время определения класса — строка 25. При запуске программы, конструктор этого объекта сработает даже до входа в главную функцию. Это видно на следующем снимке:

конструктор класса c++, деструктор класса c++, конструктор и деструктор класса с++

программа еще не дошла до выполнения строки 29 setlocale(LC_ALL, "rus"); , а конструктор уже «отчитался», что сработал (кириллица отобразилась некорректно). В строке 30 — смотрим, что содержат поля класса. Второй раз конструктор сработает в строке 32, во время создания объекта obj2.

Деструктор (от destruct — разрушать) — так же особый метод класса, который срабатывает во время уничтожения объектов класса. Чаще всего его роль заключается в том, чтобы освободить динамическую память, которую выделял конструктор для объекта. Имя его, как и у конструктора, должно соответствовать имени класса. Только перед именем надо добавить символ ~

Добавим деструктор в предыдущий код. И создадим в классе два конструктора: один будет принимать параметры, второй — нет.

Деструктор определен в строках 34 — 37. Для простоты примера он просто отобразит строку в том месте программы, где сработает. Строка 43 — объявляем объект класса и передаем данные для записи в поля. Тут сработает конструктор с параметрами. А в строке 46 — сработает конструктор по умолчанию.

конструктор класса c++, деструктор класса c++, конструктор и деструктор класса с++

Видим, что деструктор сработал автоматически и дважды (так как в программе было два объекта класса). Он срабатывает тогда, когда работа программы завершается и уничтожаются все данные.

Важное:

  • Конструктор и деструктор должны быть public;
  • Конструктор и деструктор не имеют типа возвращаемого значения;
  • Имена класса, конструктора и деструктора должны совпадать;
  • Конструктор может принимать параметры. Деструктор не принимает параметры;
  • При определении деструктора перед именем надо добавить символ ~ ;
  • Конструкторов может быть несколько, но их сигнатура должна отличаться (количеством принимаемых параметров, например);
  • Деструктор в классе должен быть определен только один.

Чтобы поддержать наш сайт — нажмите на копилку и выберите любой удобный способ.

Рассылка новых уроков по программированию:

PureCodeCpp — Основы программирования на C++ для начинающих