C++ — Двусвязный список. Перегрузка оператора.


Содержание

Перегрузка » insert_first » для двойного связанного списка. Это должна быть простая процедура, и раньше я перегружал операторов, но я застрял на этом. Я пытаюсь перегрузить его как:

Мой файл .h кажется прекрасным и похож на другие онлайн:

и моя функция insert_first также работает нормально:

Ошибка, которую я получаю, является segfault. Любые идеи или комментарии будут высоко оценены.

хорошо, так как предлагаемые комментарии, мне нужно было что-то вернуть от оператора, как

двусвязный список c++

2 ответа

Делали уже,смотри ТУТ.Там я приводил пример шаблонного двусвязного списка,с операторами.

str(); //деструктор очищает динамически выделенную память
str(const str &); //конструктор копий
void echo_data(); //функция выводит в стандартный поток длину и содержимое строки
str operator=(str); //перегруженный оператор присваивания
>;

str::str()
<
cout maxl) data=k; //и присваивает строке значение; если оно не заданное явно, то присваивается значение по умолчанию
>

Перегрузка операторов в C++. Способы применения

В прошлой части мы рассмотрели основные аспекты использования перегрузки операторов. В этом материалы вашему вниманию будут представлены перегружаемые операторы C++. Для каждого раздела характерна семантика, т.е. ожидаемое поведение. Кроме того, будут показаны типичные способы объявления и реализации операторов.

В примерах кода X означает пользовательский тип, для которого реализован оператор. T — это необязательный тип, пользовательский либо встроенный. Параметры бинарного оператора будут называться lhs и rhs . Если оператор будет объявлен как метод класса, у его объявления будет префикс X:: .

operator=

  • Определение справа налево: в отличие от большинства операторов, operator= правоассоциативен, т.е. a = b = c означает a = (b = c) .

Копирование

  • Семантика: присваивание a = b . Значение или состояние b передаётся a . Кроме того, возвращается ссылка на a . Это позволяет создавать цепочки вида c = a = b .
  • Типичное объявление: X& X::operator= (X const& rhs) . Возможны другие типы аргументов, но используется это нечасто.
  • Типичная реализация:

Перемещение (начиная с C++11)

  • Семантика: присваивание a = temporary() . Значение или состояние правой величины присваивается a путём перемещения содержимого. Возвращается ссылка на a .
  • Типичные объявление и реализация:
  • Сгенерированный компилятором operator= : компилятор может создать только два вида этого оператора. Если же оператор не объявлен в классе, компилятор пытается создать публичные операторы копирования и перемещения. Начиная с C++11 компилятор может создавать оператор по умолчанию:

Сгенерированный оператор просто копирует/перемещает указанный элемент, если такая операция разрешена.

operator+, -, *, /, %

  • Семантика: операции сложения, вычитания, умножения, деления, деления с остатком. Возвращается новый объект с результирующим значением.
  • Типичные объявление и реализация:

Обычно, если существует operator+ , имеет смысл также перегрузить и operator+= для того, чтобы использовать запись a += b вместо a = a + b . Если же operator+= не перегружен, реализация будет выглядеть примерно так:

Унарные operator+, –

  • Семантика: положительный или отрицательный знак. operator+ обычно ничего не делает и поэтому почти не используется. operator- возвращает аргумент с противоположным знаком.
  • Типичные объявление и реализация:

operator >

  • Семантика: во встроенных типах операторы используются для битового сдвига левого аргумента. Перегрузка этих операторов с именно такой семантикой встречается редко, на ум приходит лишь std::bitset . Однако, для работы с потоками была введена новая семантика, и перегрузка операторов ввода/вывода весьма распространена.
  • Типичные объявление и реализация: поскольку в стандартные классы iostream добавлять методы нельзя, операторы сдвига для определённых вами классов нужно перегружать в виде свободных функций:

Кроме того, тип левого операнда может быть любым классом, которые должен вести себя как объект ввода/вывода, то есть правый операнд может быть и встроенного типа.


Бинарные operator&, |, ^

  • Семантика: Битовые операции “и”, “или”, “исключающее или”. Эти операторы перегружаются очень редко. Опять же, единственным примером является std::bitset .

operator+=, -=, *=, /=, %=

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

operator&=, |=, ^=, >=

  • Семантика: аналогична operator+= , но для логических операций. Эти операторы перегружаются так же редко, как и operator| и т.д. operator и operator>>= не используются для операций ввода/вывода, поскольку operator и operator>> уже изменяют левый аргумент.

operator==, !=

  • Семантика: проверка на равенство/неравенство. Смысл равенства очень сильно зависит от класса. В любом случае, учитывайте следующие свойства равенств:
    1. Рефлексивность, т.е. a == a .
    2. Симметричность, т.е. если a == b , то b == a .
    3. Транзитивность, т.е. если a == b и b == c , то a == c .
  • Типичные объявление и реализация:

Вторая реализация operator!= позволяет избежать повторов кода и исключает любую возможную неопределённость в отношении любых двух объектов.

operator , >=

  • Семантика: проверка на соотношение (больше, меньше и т.д.). Обычно используется, если порядок элементов однозначно определён, то есть сложные объекты с несколькими характеристиками сравнивать бессмысленно.
  • Типичные объявление и реализация:

Реализация operator> с использованием operator или наоборот обеспечивает однозначное определение. operator может быть реализован по-разному, в зависимости от ситуации. В частности, при отношении строго порядка operator== можно реализовать лишь через operator :

operator++, –

  • Семантика: a++ (постинкремент) увеличивает значение на 1 и возвращает старое значение. ++a (преинкремент) возвращает новое значение. С декрементом operator— все аналогично.
  • Типичные объявление и реализация:

operator()

  • Семантика: исполнение объекта-функции (функтора). Обычно используется не для изменения объекта, а для использования его в качестве функции.
  • Нет ограничений на параметры: в отличие от прошлых операторов, в этом случае нет никаких ограничений на количество и тип параметров. Оператор может быть перегружен только как метод класса.
  • Пример объявления:

operator[]

  • Семантика: доступ к элементам массива или контейнера, например, в std::vector , std::map , std::array .
  • Объявление: тип параметра может быть любым. Тип возвращаемого значения обычно является ссылкой на то, что хранится в контейнере. Часто оператор перегружается в двух версиях, константной и неконстантной:

operator!

  • Семантика: отрицание в логическом смысле.
  • Типичные объявление и реализация:

explicit operator bool

  • Семантика: использования в логическом контексте. Чаще всего используется с умными указателями.
  • Реализация:

operator&&, ||

  • Семантика: логические “и”, “или”. Эти операторы определены только для встроенного логического типа и работают по “ленивому” принципу, то есть второй аргумент рассматривается, только если первый не определяет результат. При перегрузке это свойство теряется, поэтому перегружают эти операторы редко.

Унарный operator*

  • Семантика: разыменовывание указателя. Обычно перегружается для классов с умными указателями и итераторами. Возвращает ссылку на то, куда указывает объект.
  • Типичные объявление и реализация:

operator->

  • Семантика: доступ к полю по указателю. Как и предыдущий, этот оператор перегружается для использования с умными указателями и итераторами. Если в коде встречается оператор -> , компилятор перенаправляет вызовы на operator-> , если возвращается результат пользовательского типа.
  • Usual implementation:

operator->*

  • Семантика: доступ к указателю-на-поле по указателю. Оператор берёт указатель на поле и применяет его к тому, на что указывает *this , то есть objPtr->*memPtr — это то же самое, что и (*objPtr).*memPtr . Используется очень редко.
  • Возможная реализация:

Здесь X — это умный указатель, V — тип, на который указывает X , а T — тип, на который указывает указатель-на-поле. Неудивительно, что этот оператор редко перегружают.

Унарный operator&

  • Семантика: адресный оператор. Этот оператор перегружают очень редко.

operator,

  • Семантика: встроенный оператор “запятая”, применённый к двум выражениям, выполняет их оба в порядке записи и возвращает значение второго из них. Перегружать его не рекомендуется.

operator

  • Семантика: оператор побитовой инверсии. Один из наиболее редко используемых операторов.

Операторы приведения типов

  • Семантика: позволяет скрытое или явное приведение объектов класса к другим типам.
  • Объявление:

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


operator new, new[], delete, delete[]

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

Заключение

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

Перегрузка операторов Operator overloading

Ключевое слово operator объявляет функцию, которая указывает, что означает символ оператора при применении к экземплярам класса. The operator keyword declares a function specifying what operator-symbol means when applied to instances of a class. Это дает оператору более одного значения — «перегружает» его. This gives the operator more than one meaning, or «overloads» it. Компилятор различает разные значения оператора, проверяя типы его операндов. The compiler distinguishes between the different meanings of an operator by examining the types of its operands.

Синтаксис Syntax

Тип operator символ оператора ( список параметров ) type operator operator-symbol ( parameter-list )

Примечания Remarks

Функцию большинства встроенных операторов можно переопределить глобально или для отдельных классов. You can redefine the function of most built-in operators globally or on a class-by-class basis. Перегруженные операторы реализуются в виде функции. Overloaded operators are implemented as functions.

Перегруженный оператор имеет имя вида operator x, где x — это оператор из приведенной ниже таблицы. The name of an overloaded operator is operator x, where x is the operator as it appears in the following table. Например, для перегрузки оператора сложения следует определить функцию с именем operator +. For example, to overload the addition operator, you define a function called operator+. Аналогичным образом, для перегрузки оператора сложения с присваиванием, +=, следует определить функцию с именем operator +. Similarly, to overload the addition/assignment operator, +=, define a function called operator+=.

Переопределяемые операторы Redefinable Operators

Оператор Operator Имя Name Тип Type
, , Запятая Comma Бинарный Binary
! ! Логическое НЕ Logical NOT Унарный Unary
!= != Неравенство Inequality Бинарный Binary
% Модуль Modulus Бинарный Binary
%= Назначение модуля Modulus assignment Бинарный Binary
& Побитовое И Bitwise AND Бинарный Binary
& Взятие адреса Address-of Унарный Unary
&& Логическое И Logical AND Бинарный Binary
&= Назначение побитового И Bitwise AND assignment Бинарный Binary
( ) ( ) Вызов функции Function call — —
( ) ( ) Оператор приведения типа Cast Operator Унарный Unary
* * Умножение Multiplication Бинарный Binary
* * Разыменование указателя Pointer dereference Унарный Unary
*= *= Присваивание умножения Multiplication assignment Бинарный Binary
+ Сложение Addition Бинарный Binary
+ Унарный плюс Unary Plus Унарный Unary
++ Приращение 1 Increment 1 Унарный Unary
+= Присваивание сложения Addition assignment Бинарный Binary
Вычитание Subtraction Бинарный Binary
Унарное отрицание Unary negation Унарный Unary
Уменьшение 1 Decrement 1 Унарный Unary
-= Присваивание вычитания Subtraction assignment Бинарный Binary
-> Выбор члена Member selection Бинарный Binary
->* ->* Выбор указателя на член Pointer-to-member selection Бинарный Binary
/ Деление Division Бинарный Binary
/= Присваивание деления Division assignment Бинарный Binary
Меньше Less than Бинарный Binary
Сдвиг влево Left shift Бинарный Binary
Сдвиг влево и присваивание Left shift assignment Бинарный Binary
Меньше или равно Less than or equal to Бинарный Binary
= Присваивание Assignment Бинарный Binary
== Равенство Equality Бинарный Binary
> Больше Greater than Бинарный Binary
>= Больше или равно Greater than or equal to Бинарный Binary
>> Сдвиг вправо Right shift Бинарный Binary
>>= Сдвиг вправо и присваивание Right shift assignment Бинарный Binary
[ ] [ ] Нижний индекс массива Array subscript — —
^ Исключающее ИЛИ Exclusive OR Бинарный Binary
^= Исключающее ИЛИ/присваивание Exclusive OR assignment Бинарный Binary
| | Побитовое включающее ИЛИ Bitwise inclusive OR Бинарный Binary
|= |= Назначение побитового включающего ИЛИ Bitwise inclusive OR assignment Бинарный Binary
|| || Логическое ИЛИ Logical OR Бинарный Binary
Дополнение до единицы One’s complement Унарный Unary
delete delete Оператор delete Delete — —
new new Оператор new New — —
операторы преобразования conversion operators операторы преобразования conversion operators Унарный Unary

1 Существуют две версии унарных операторов увеличения и уменьшения: префиксная и постфиксная. 1 Two versions of the unary increment and decrement operators exist: preincrement and postincrement.

Дополнительные сведения см. в разделе Общие правила перегрузки операторов. See General Rules for Operator Overloading for more information. Ограничения для разных категорий перегруженных операторов описываются в следующих разделах: The constraints on the various categories of overloaded operators are described in the following topics:

Перегрузка операций

Кроме перегрузки функций С++ позволяет организовать перегрузку операций. Механизм перегрузки операций позволяет обеспечить более традиционную и удобную запись действий над объектами. Для перегрузки встроенных операторов используется ключевое слово operator .

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


где @ — знак перегружаемой операции (-, +, * и т.д.),
тип — тип возвращаемого значения.

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

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

Любой перегруженный оператор можно вызвать с использованием функциональной формы записи (функции-операции):

Функция-операция описывается и может вызываться так же, как любая другая функция. Использование операции – это лишь сокращенная запись явного вызова функции операции.

Имеется два способа описания функции, соответствующей переопределяемой операции:

  • если функция задается как обычный метод класса, то первым операндом ее является объект класса, указатель на который передается неявным указателем this ;
  • если первый операнд переопределяемой операции не является объектом некоторого класса, либо требуется передавать в качестве операнда не указатель на объект, а некоторое значение, то соответствующая функция должна быть определена как дружественная классу с полным списком аргументов.

Можно описывать функции, определяющие значения следующих операций:

! = += -= *= /= %= ^= &= |= > >>= = && || ++ — [] () new delete

Операции, не допускающие перегрузки:

  • . прямой выбор члена объекта класса;
  • .* обращение к члену через указатель на него;
  • ? : условная тернарная операция;
  • :: операция указания области видимости (разрешение контекста);
  • sizeof операция вычисления размера в байтах;
  • # препроцессорная операция.

Правила перегрузки операций

  • Язык C++ не допускает определения для операций нового лексического символа, кроме уже определенных в языке. Например, нельзя определить в качестве знака операции @ .
  • Не допускается перегрузка операций для встроенных типов данных. Нельзя, например, переопределить операцию сложения целых чисел:

Перегрузка унарной операции

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

Если унарная операция перегружается дружественной функцией, то она должна иметь один аргумент – объект, для которого она выполняется.
Таким образом, для любой унарной операции @ aa@ или @aa может интерпретироваться или как aa.operator@() , или как operator @(aa) .

Если определена и та, и другая, то и aa@ и @aa являются ошибками.

Функция- член класса Дружественная функция

Перегрузка бинарной операции

Если бинарная операция перегружается с использованием метода класса, то в качестве своего первого аргумента она получает неявно переданную переменную класса (указатель this на объект), а в качестве второго — аргумент из списка параметров. То есть, фактически бинарная операция, перегружаемая методом класса, имеет один аргумент (правый операнд), а левый передается неявно через указатель this .

Если бинарная операция перегружается дружественной функцией, то в списке параметров она должна иметь оба аргумента:

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

то необходимо переопределить операцию сложения дважды:


Перегрузка операций индексирования и вызова функции

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

Перегрузка операции присваивания

Любой конструктор вызывается явно либо неявно в том случае, если необходимо создать новый объект какого-либо класса.
Рассматривая на примере создание нового объекта и его инициализацию, мы использовали конструктор копии. До проведения инициализации существовал только один объект. Второй был создан и инициализирован в результате работы конструктора копии. Однако если бы существовало несколько объектов одного типа, и была бы необходимость в присваивании значений одного объекта элементам другого, то никакой из конструкторов не вызывается, так как объект уже был создан. При выполнении операции присваивания по умолчанию копирование значений происходит «поверхностно», но такое копирование не всегда допустимо. Например, недопустимо копирование массивов или указателей.
Если необходимо осуществить присваивание, но поведение операции присваивания по умолчанию не устраивает, то операция присваивания может быть перегружена.
Дополним приведенный выше пример реализации класса String перегруженным оператором присваивания:

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

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

где void * – указатель на область памяти, выделяемую под объект,
size – размер объекта в байтах,
size_t – тип размерности области памяти, int или long int .
Переопределение этих операций позволяет написать собственное распределение памяти для объектов класса.

Урок №133. Перегрузка операторов ввода и вывода

Обновл. 18 Июн 2020 |

В этом уроке мы рассмотрим перегрузку операторов ввода и вывода.

Перегрузка оператора вывода class Point

Если вы захотите вывести объект этого класса на экран, то вам нужно будет сделать что-то вроде следующего:

Конечно, было бы проще написать отдельную функцию для вывода, которую можно было бы повторно использовать. Например, функцию print():

Теперь уже намного лучше, но здесь также есть свои нюансы. Поскольку print() возвращает void, то эту функцию нельзя вызывать в середине стейтмента вывода. Вместо этого стейтмент вывода придётся разбить на несколько частей (строк):

А вот если бы мы могли просто написать:

И получить тот же результат, но без необходимости разбивать стейтмент вывода на несколько строк и помнить название функции вывода. К счастью, это можно сделать, перегрузив оператор вывода std::cout . Если оператором является // std::cout — это объект std::ostream

Реализация перегрузки оператора #include

Всё довольно просто. Обратите внимание, насколько проще стал стейтмент вывода по сравнению с другими стейментами из примеров выше. Наиболее заметным отличием является то, что std::cout стал параметром out в нашей функции перегрузки (который затем станет ссылкой на std::cout при вызове этого оператора).

Самое интересное здесь — тип возврата. С перегрузкой арифметических операторов мы вычисляли и возвращали результат по значению. Однако, если вы попытаетесь возвратить std::ostream по значению, то получите ошибку компилятора. Это случится из-за того, что std::ostream запрещает своё копирование.

В этом случае мы возвращаем левый параметр в качестве ссылки. Это не только предотвращает создание копии std::ostream, но также позволяет нам «связать» стейтменты вывода вместе, например std::cout .

Вы могли бы подумать, что, поскольку оператор std::cout , то, учитывая правила приоритета/ассоциативности, он будет обрабатывать это выражение как (std::cout . Тогда std::cout приведёт к вызову функции перегрузки оператора void — в этом нет смысла!

Возвращая параметр out в качестве возвращаемого значения выражения (std::cout мы возвращаем std::cout и вторая часть нашего выражения обрабатывается как std::cout — вот где сила!

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

C++ — Двусвязный список. Перегрузка оператора.

Собственно, всё написано у Шилдта, это, скорее, шпаргалка.

Совсем нельзя перегружать: . :: .* ?

Нельзя перегружать не членами класса: = () [] ->

Не надо перегружать, если не какой-то особый случай: new delete -> ->* ,

Если функция перегрузки оператора — член класса, то при перегрузке бинарного оператора неявно (через указатель this ) передаётся первый операнд (слева от знака операции), а параметром функции явно передаётся второй операнд (справа от знак операции).

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

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

Если функция перегрузки оператора — не член, но друг ( friend ) класса, то при перегрузке бинарного оператора явно передаются оба операнда (стоящие слева и справа от знака операции), а при перегрузке унарного оператора явно передаётся ссылка на его единственный операнд. При этом префиксная форма функции-друга принимает один параметр-ссылку (который и является операндом), а постфиксная форма — два параметра (вторым является целочисленное значение, которое не используется).

Перегруженные операторы отношения или логических операцией могут возвращать просто значение типа int или bool .

Вообще говоря, перегрузка бинарной операции, присваивания и унарных постфиксных операторов должны возвращать новый объект класса, созданный внутри операторной функции. Перегрузка унарного префиксного оператора может возвращать как объект класса значение *this . Можно возвращать *this и для перегруженных операций вроде *= , += и т.д. Делать операторные функции с типом возвращаемого значения void не нужно — над объектами не будет работать, например, присваивание по цепочке вида a=b=c .

Для класса с динамически выделяемой под некоторые свойства оперативной памятью нужен явный конструктор копирования, принятое по умолчанию побитовое копирование объектов не подходит (при этом могут быть скопированы адреса динамических объектов, но не их значения). Конструктор копирования должен иметь единственный параметр — const-ссылку на объект класса (копируемый объект; объект, куда копируем, доступен через this ).

Корректно переопределённый оператор [] (который может использоваться и слева, и справа от знака «=») должен возвращать ссылку на объект того типа, из которого состоит индексируемая последовательность. В принципе, [] перегружается как бинарный оператор.

Оператор вызова функций () создаёт не новый способ вызова функций, а операторную функцию, которой можно передать произвольное число параметров.

Пример на большую часть сказанного, простейший класс Class состоит только из числа n :

03.04.2014, 19:40; рейтинг: 12144

Перегрузка операторов для добавления двух двусвязных списков — c++

Мне нужна помощь при перегрузке оператора «+» для добавления двух дважды связанных списков. Я не могу скомпилировать свою программу из-за ошибки «no match for operator =. «. Я уже перегрузил оператор =, но попытаюсь напечатать результат добавления к std-выводу. Я также перегрузил оператор

EDIT: Ошибки, создаваемые компилятором (g++, выводятся из кода :: blocks, я удалил заметки компилятора):

    2 2
  • 18 июл 2020 2020-07-18 06:12:31
  • Andrzej Smyk

2 ответа

В вашем коде есть одна вопиющая ошибка:

Этот код вызывает неопределенное поведение, так как вы не возвращаете значение. Это должно быть так:

Кроме того, вы должны передать свой LList по ссылке const в функции, которые не меняют параметр LList :

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

operator ищет объекты non-const LList , но добавление «inline» возвращает временный LList (что будет означать, что возвращаемое значение будет const ). Код не будет компилироваться из-за вашего перегруженного operator принятия только не const LList .

Поэтому вам нужно изменить свой параметр в operator на const LList& .

  • 18 июл 2020 2020-07-18 06:12:31
  • PaulMcKenzie

У вас есть встроенный «текущий» указатель в вашем классе списка. Это серьезная ошибка дизайна. Из-за этой ошибки вы не можете правильно определить свои функции.

Это ошибка дизайна, потому что при таком дизайне вы не можете перебирать списки const , а это означает, среди прочего, что вы не можете ничего использовать со временными списками. Поэтому, когда вы вычисляете setA + setB , вы не можете назначить его чему-либо, потому что для назначения вам потребуется итерация, поэтому вам нужен аргумент non-const для operator= и для конструктора копирования. Но вы не можете привязать временную ссылку к неконстантной ссылке.

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

Вы также не можете взять список и передать его некоторой функции в середине итерации и ожидать продолжения итерации с места, которое вы оставили, потому что все, что итерации в списке, меняет указатель на curr .

Лучшее решение — избавиться от члена curr и сделать большинство ваших аргументов const LList& . Хотя это не основное решение, есть много других недостатков в том, что текущий указатель встроен в класс списка, поэтому я не буду говорить о них.

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

В вашем случае итераторами могут быть Node* и const Node* . Вам нужно только предоставить функции-члены, которые возвращают первый узел в списке, который у вас уже есть. Реальные библиотеки делают то же самое. Обычно они обертывают указатель узла в отдельном классе итератора по разным причинам; но для простой домашней работы это не обязательно (хотя вы можете сделать это, если хотите).

Список list в С++: полный материал

Всем привет! Не давно мы прошли вектор в C++, поэтому сегодня мы решили снова затронуть тему контейнеров и подготовили материал об еще одном контейнере — list.

Что такое список list

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

На картинке ниже показана, как это устроено:

У двусвязного списка нет индексов, но вместо их в C++ есть итераторы.

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

На примере выше в начале было два элемента, потом мы решили добавить один элемент между ними.

А так совершается удаление.

Как создать список list

Сначала подключаем библиотеку —
.

Далее используем конструкцию ниже:

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

Вот пример создания списка с типом string :

Как добавить элементы при создании списка

Чтобы сразу после создания списка присвоить ему значения нужно сделать так:

Такой способ можно использовать только в C++ 11 и выше.

Методы списка list

Вот функции которые можно применять в своей программе вместе со списком (нажмите на их имена чтобы перейти на страницу с полным руководством):

Функция- член класса Дружественная функция
Имя функции Описание
pop_front удалить элемент в начале
pop_back удалить элемент в конце
push_front добавить элемент в начала
push_back добавить элемент в конец
front обратится к первому элементу
back обратиться к последнему элементу
insert добавить элемент в какое-то место
copy вывести все элементы списка (и не только)
unique удалить все дубликаты
merge добавление другого списка

Давайте с несколькими методами познакомимся подробнее.

insert

С помощью его можно добавить новый элемент в любую часть контейнера (в нашем случае для списка). Вот как он работает:

  • Первым аргументом передаем — местоположение. Оно указывается итератором, что это читайте вот здесь.
  • Вторым значение новой ячейки. Здесь может быть как переменная так и просто значение (5 например).

Вообще он имеет несколько видов применения:

  • Вывод элементов.
  • Запись элементов.
  • А также копирования какого-то количества ячеек и вставка их в позицию Y.

Чтобы его использовать дополнительно нужно подключить библиотеку — .

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

Первые два значения ( myspisok.begin() , myspisok.end() ) которые должны передать, — это итераторы начала и конца контейнера.

Дальше используем итератор вывода — ostream_iterator (cout,» «) . В кавычках указывается значение между элементами (в нашем случае это пробел).

unique

Удаляет все повторяющиеся элементы (дубликаты). Использовать его очень просто:

Параметризованный класс двухсвязного списка

Реализация двусвязного списка

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

Вот как выглядит общая форма объявления параметризованного (обобщенного) класса.

template class имя_класса <

Рассмотрим это объявление. Здесь Tтип_данных представляет собой имя типа шаблона, которое в каждом случае конкретизации будет замещаться фактическим типом данных. При необходимости, можно определить более одного параметризованного типа данных, используя список с разделителем-запятой, т.е. параметризованные классы могут иметь несколько аргументов шаблона. Заметим, что в пределах определения класса имя Tтип_данных можно использовать в любом месте. Когда создан параметризованный класс, Вы можете создать конкретную реализацию этого класса, используя следующий синтаксис:

В приведенном синтаксисе тип_данных представляет собою имя типа данных, над которыми фактически оперирует класс, и заменяет собой переменную Tтип_данных

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

template class list <
data t data;
list *next;

list *prev;
public:

void add(list *node) < node->next = this; next = 0; >
list *getnext() < return next; >
data_t getdata()

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

Вставка узла в двусвязном списке происходит следующим образом:1Выделить память под новый узел.2Записать в новый узел значение.3.В указатель на предыдущий узел записать адрес узла, который должен располагаться перед новым узлом.4.Записать в указатель на следующий узел адрес узла, который должен быть расположен после нового узла.

После этого уже не имеет значения, в каком узле (предыдущем или следующем) первым поменять значение адреса на адрес нового узла. 5В предыдущем узле заменяем значение указателя на следующий узел на адрес нового узла.6. В следующем узле заменяем значение указателя на предыдущий узел на адрес нового узла.

Удаление узла.Для удаления узла в двусвязном списке нужно:1.Записать адрес узла, следующего за удаляемым узлом, в указатель на следующий узел узла, являющегося предыдущим для удаляемого узла.2

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

Удалить узел, предназначенный для удаления.

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

54. Перегрузка методов класса.

#include
#include
#include
class Re
<
public:
int method(int arg)
<
return arg*arg*arg;
>
double method(double arg)
<
return std::tan(arg);
>
>;
int main(int argc, char* argv[])
<
Re obj;
std::cout

Когда одинаковые по смыслу операции применяются к операндам различных типов, их вынужденно приходится называть по-разному. Невозможность применять для разных типов функции с одним именем приводит к необходимости выдумывать различные имена для одного и того же, что создаёт путаницу, а может и приводить к ошибкам. Например, в классическом языке Си существует два варианта стандартной библиотечной функции нахождения модуля числа: abs() и fabs() — первый предназначен для целого аргумента, второй — для вещественного. Такое положение, в сочетании со слабым контролем типов Си, может привести к труднообнаруживаемой ошибке: если программист напишет в вычислении abs(x), где x — вещественная переменная, то некоторые компиляторы без предупреждений сгенерируют код, который будет преобразовывать x к целому путём отбрасывания дробной части и вычислять модуль от полученного целого числа!

55. Перегрузка операторов new и delete
Для перегрузки new следует использовать прототип функции вида: void * operator new(size_t size); В дальнейшем обращение к оператору new для выделения памяти объектам класса будут перенаправлены замещающей функции. Функция должна возвращать адрес памяти, выделенной объекту. Вместо кучи можно выделять память из статического буфера, на диске или другом запоминающем устройстве.
// Программа 6
#include

class DemoNew <
private:
int x;
public:
DemoNew();
void * operator new(size_t size);
>;
char buf[512];
int index;
void main()
<
cout if ( index >=512 – sizeof(DemoNew)) //Проверка наличия
return 0; //пространства в buf
else <
int k = index;
index+= sizeof(DemoNew);
return &buf[k];
>
>
Оператор delete служит для удаления объектов, адресованных указателями. Прототип функции перегрузки оператора delete должен иметь вид: void operator delete(void *p); , где р ссылается на удаляемый объект.
void operator delete(void *p);
void DemoNew::operator delete(void *p)
<
cout > может быть перегружена следующим образом:

istream& operator >> (istream& s, info &m)< // Ввод в info

s.width(30); s >> m.name;

Для считывания строки ввода, такой как «Resistance 300 Ohm», можно использовать следующую запись:

Цукерберг рекомендует:  cURL для начинающих. Примеры использования
Понравилась статья? Поделиться с друзьями:
Все языки программирования для начинающих