Не могу понять ссылки в C++
От: Worminator X Россия #StandWithPalestine 🖤🤍💚
Дата: 15.06.24 05:42
Оценка:
Несколько раз пытался освоить C++, и всегда забрасывал это дело — сложный, совершенно непонятный язык.

Вот, допустим, совершенно тривиальный код обхода списка целых чисел на чистом Си с указателями:
  main.c
#include <stdio.h>
#include <stdlib.h>

struct INTEGER_LIST {
    int number;
    struct INTEGER_LIST *next;
};

void print_integer_list(struct INTEGER_LIST *list) {
    struct INTEGER_LIST *current;
    for (current = list; current; current = current->next) {
        printf(current == list ? "%d" : ", %d", current->number);
    }
    putchar('\n');
}

void free_integer_list(struct INTEGER_LIST *list) {
    struct INTEGER_LIST *next;
    while (list) {
        next = list->next;
        free(list);
        list = next;
    }
}

struct INTEGER_LIST *new_integer_list(int min, int max) {
    int i;
    struct INTEGER_LIST *list, *next;
    if (min >= max) return NULL;
    list = NULL;
    for (i = max; i >= min; i--) {
        next = list;
        list = (struct INTEGER_LIST*)malloc(sizeof(struct INTEGER_LIST));
        if (!list) {
            free_integer_list(next);
            return NULL;
        }
        list->number = i;
        list->next = next;
    }
    return list;
}

int main(int argc, char *argv[]) {
    struct INTEGER_LIST *numbers;
    numbers = new_integer_list(1, 3);
    if (!numbers) return 1;
    print_integer_list(numbers);
    free_integer_list(numbers);
    return 0;
}


Можно переписать его в стиле "Си с классами", здесь пока тоже все очевидно, просто теперь new/delete вместо malloc/free и class вместо struct:
  main.cpp
#include <iostream>

class List {
private:
    List *next;
public:
    List *getNext() {
        return this->next;
    }
    List(List *next) {
        this->next = next;
    }
};

class IntegerList: public List {
private:
    int number;
public:
    int getNumber() {
        return this->number;
    }
    IntegerList(int number, List *next): List(next) {
        this->number = number;
    }
};

void printIntegerList(List *list) {
    for (List *current = list; current; current = current->getNext()) {
        if (current != list) {
            std::cout << ',' << ' ';
        }
        std::cout << ((IntegerList*)current)->getNumber();
    }
    std::cout << std::endl;
}

void printIntegerList(List &list) {
    printIntegerList(&list);
}

int main(int argc, char *argv[]) {
    List *numbers = new IntegerList(1, new IntegerList(2, new IntegerList(3, NULL)));
    printIntegerList(numbers);
    while (numbers) {
        List *next = numbers->getNext();
        delete numbers;
        numbers = next;
    }
    return 0;
}


Примерно так я представлял себе C++ в школе и универе, не зная ни про ссылки, ни про операторы, ни про шаблоны и имея смутное представление о множественном наследовании.
Т.к. Pure C, Turbo Pascal и Delphi (и чуть позже Java с C#) решали все необходимые задачи, изучать C++ тогда не было никакой необходимости.

По правилам хорошего тона в C++ нужно всегда использовать ссылки вместо указателей (кроме интеграции с кодом на Си) и по возможности не управлять памятью вручную, т.к. это чревато утечками.
Я попытался переписать код, и вышло примерно следующее (для упрощения пока без шаблонов):
  main.cpp
#include <iostream>

using namespace std;

enum ListType {
    LT_EMPTY_LIST,
    LT_INTEGER_LIST
};

class List {
public:
    virtual ListType getType() const = 0;
    List() {}
};

class EmptyList: public List {
public:
    virtual ListType getType() const override {
        return LT_EMPTY_LIST;
    }
    EmptyList(int x): List() {}
};

class IntegerList: public List {
private:
    const int number;
    const List &next;
public:
    virtual ListType getType() const override {
        return LT_INTEGER_LIST;
    }
    int getNumber() const {
        return number;
    }
    const List &getNext() const {
        return next;
    }
    IntegerList(
        int number,
        const List &next
    ): List(next), number(number), next(next) {}
};

void recursivePrintIntegerList(const IntegerList &list, int counter) {
    cout << list.getNumber();
    if (list.getNext().getType() == LT_INTEGER_LIST) {
        cout << ',' << ' ';
        recursivePrintIntegerList((const IntegerList&)list.getNext(), counter + 1);
    }
    if (counter == 1) {
        cout << endl;
    }
}

void recursivePrintIntegerList(const IntegerList &list) {
    recursivePrintIntegerList(list, 1);
}

void foreachPrintIntegerList(const IntegerList &list) {
    int counter = 0;
    for (
        List &current = (List&)list;
        current.getType() == LT_INTEGER_LIST;
        current = ((IntegerList&)current).getNext()
    ) {
        if (counter > 10) break;
        if (counter > 0) {
            cout << ',' << ' ';
        }
        counter++;
        cout << ((IntegerList&)current).getNumber();
    }
    cout << endl;
}

int main(int argc, char *argv[]) {
    const EmptyList empty(0);
    const IntegerList n3(3, empty);
    const IntegerList n2(2, n3);
    const IntegerList n1(1, n2);
    const IntegerList numbers(1, IntegerList(2, IntegerList(3, empty)));
    cout << n1.getNumber() << ',' << ' ' << n2.getNumber() << ',' << ' ' << n3.getNumber() << endl;
    cout << numbers.getNumber() << ',' << ' ';
    cout << ((IntegerList&)numbers.getNext()).getNumber() << ',' << ' ';
    cout << ((IntegerList&)(
        (IntegerList&)(
            (IntegerList&)numbers.getNext()
        ).getNext()
    )).getNumber() << endl;
    recursivePrintIntegerList(numbers);
    foreachPrintIntegerList(numbers);
    return 0;
}

Помимо того, что этот талмуд совершенно непонятен, оно еще и не работает.
Рекурсивный обход списка в recursivePrintIntegerList вроде выводит все как положено, но в foreachPrintIntegerList цикл почему-то выводит одни единицы и не хочет переходить на следующий элемент.
Что нужно исправить? И что можно почитать по теме?
Из учебника Столярова так и не понял, что такое есть ссылка в C++ (вроде синтаксический сахар над указателем, но как-то странно и непонятно работает).
И еще, почему, если в EmptyList сделать пустой конструктор, то на строки const EmptyList empty(); const IntegerList n3(3, empty); компилятор ругается? Вариант с const EmptyList empty(void) не помог.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Re: Не могу понять ссылки в C++
От: andrey.desman  
Дата: 15.06.24 06:28
Оценка: 9 (3) +1
Здравствуйте, Worminator X, Вы писали:

WX>Из учебника Столярова так и не понял, что такое есть ссылка в C++ (вроде синтаксический сахар над указателем, но как-то странно и непонятно работает).


Ссылки — это не совсемсинтаксический сахар над указателями. По крайней мере не в смысле определения, хотя их и можно использовать как таковые. Ссылка — это альтернативное имя для какого-то выражения, алиас.
Саму ссылку нельзя изменить, она привязывается (bind) к чему-то в момент определения и остается привязанной к этому выражению до конца своего существования.
Грубо говоря, ссылка ведет себя как разыменованный константный указатель. Не указатель на константу, а именно константный указатель. А, например, константная ссылка как константный указатель на константу (const * const).

// ссылка
int a[5][5];
int& b = a[1][1];
b = 10; // эквивалентно a[1][1] = 10
b = a[0][0]; // эквивалентно a[1][1] = a[0][0], перепривязки здесь нет
b = 12; // снова эквивалентно a[1][1] = 12

// аналогично на указателях
int a[5][5];
int* const b = &a[1][1];
*b = 10; // эквивалентно a[1][1] = 10
*b = a[0][0]; // эквивалентно a[1][1] = a[0][0], перепривязки здесь нет
*b = 12; // снова эквивалентно a[1][1] = 12
// b = &a[0][0]; // нельзя, сам указатель b константный, его не перепривязать, как и ссылку


// константная ссылка
int a[5][5];
const int& b = a[1][1];
a[0][0] = b; // эквивалентно a[0][0] = a[1][1]
// b = ...; // нельзя

// аналогично на указателях
int a[5][5];
int const* const b = &a[1][1];
a[0][0] = *b; // эквивалентно a[0][0] = a[1][1]
// *b = ...; // нельзя
// b = ...; // нельзя


В твоем коде
    for (
        List &current = (List&)list;
        current.getType() == LT_INTEGER_LIST;
        current = ((IntegerList&)current).getNext()
    )


создается ссылка current, которая привязывается к list. Она и останется привязанной к list.
Выражение current = ((IntegerList&)current).getNext() не перепривязывает ссылку current к другому объекту, а присваивает привязанному объекту list значение из list.getNext().

Если на указателях, то это эквивалентно следующему:
    for (
        List* const current = &(List&)list; // сам указатель константный, его нельзя поменять, как и привязку ссылки
        current->getType() == LT_INTEGER_LIST;
        *current = *((IntegerList*)current)->getNext() // считаем, что getNext() тоже указатель возвращает, как во втором варианте
    )


На ссылках такой цикл написать в принципе невозможно.

WX>По правилам хорошего тона в C++ нужно всегда использовать ссылки вместо указателей (кроме интеграции с кодом на Си) и по возможности не управлять памятью вручную, т.к. это чревато утечками.


Нет такого правила. Всему свое применение.

WX>Я попытался переписать код, и вышло примерно следующее (для упрощения пока без шаблонов):


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

Что касается управления памятью вручную, то да. Вместо обычных указателей здесь лучше использовать умный std::unique_ptr.

WX>И еще, почему, если в EmptyList сделать пустой конструктор, то на строки const EmptyList empty(); const IntegerList n3(3, empty); компилятор ругается? Вариант с const EmptyList empty(void) не помог.


const EmptyList empty(); — это объявление функции empty(), возвращающей const EmptyList. Смотри https://en.wikipedia.org/wiki/Most_vexing_parse
Исправить можно инициализатором фигурными скобками const EmptyList empty{};. Или вообще их отсутствием (все равно будет вызван конструктор по умолчанию).
Отредактировано 15.06.2024 6:42 andrey.desman . Предыдущая версия . Еще …
Отредактировано 15.06.2024 6:41 andrey.desman . Предыдущая версия .
Re: Не могу понять ссылки в C++
От: reversecode google
Дата: 15.06.24 09:43
Оценка:
WX>Несколько раз пытался освоить C++, и всегда забрасывал это дело — сложный, совершенно непонятный язык.

учебник С++
плюс курсы от константина владимирова на ютубе
там у него кажется давече был курс для самых маленьких

ну или на крайняк записывайтесь в клуб Шмыг
там продвигают методику изучения С++ методом задрачивания на форуме(ах)
но судя по всему вы уже там состоите
Re: Не могу понять ссылки в C++
От: rg45 СССР  
Дата: 15.06.24 10:00
Оценка: 6 (1) +2
Здравствуйте, Worminator X, Вы писали:

WX>Несколько раз пытался освоить C++, и всегда забрасывал это дело — сложный, совершенно непонятный язык.

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

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

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

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

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

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

Ну и как вишенка на торте, распростаненный стереотип — "я хотел сделать проще, поэтому без шаблонов". По факту же все наоборот.

В качестве иллюстрации сказанного можно предложить вот такой пример реализации односвязного списка на C++:

http://coliru.stacked-crooked.com/a/33377b09832a0bd3

  UniDirList, C++
#include <cassert>
#include <iostream>
#include <memory>
#include <type_traits>

template <typename T>
struct ListNode
{
   T value{};
   std::unique_ptr<ListNode> next;

   template <typename...X>
   ListNode(X&&...x) : value{std::forward<X>(x)...}{}
};

template <typename T>
requires (!std::is_reference_v<T>)
class ListNodeIterator
{
public:
   using value_type = std::remove_const_t<T>;
   using node_type = std::conditional_t<std::is_const_v<T>, const ListNode<value_type>, ListNode<value_type>>;

   ListNodeIterator() = default;
   ListNodeIterator(node_type* node) : m_node(node) {}

   T& operator*() const { assert(m_node); return m_node->value; }
   ListNodeIterator& operator++() { assert(m_node); m_node = m_node->next.get(); return *this; }
   bool operator == (const ListNodeIterator& rhs) const { return m_node == rhs.m_node; }

private:
   node_type* m_node{};
};

template <typename T>
requires std::same_as<T, std::decay_t<T>>
class UniDirList
{
public:
   using iterator = ListNodeIterator<T>;
   using const_iterator = ListNodeIterator<const T>;

   UniDirList() = default;
   UniDirList(std::initializer_list<T> values) { for(auto&& value : values) emplace_back(value); }

   bool empty() const { return !m_first; }

   iterator begin() { return iterator{ m_first.get() }; }
   iterator end() { return {}; }

   const_iterator begin() const { return const_iterator{ m_first.get() }; }
   const_iterator end() const { return {}; }

   template <typename...X>
   T& emplace_back(X&&...x) {
      if (m_last) {
         m_last->next = std::make_unique<Node>(std::forward<X>(x)...);
         m_last = m_last->next.get();
      }
      else {
         m_first = std::make_unique<Node>(std::forward<X>(x)...);
         m_last = m_first.get();
      }
      return m_last->value;
   }

private:
   using Node = ListNode<T>;
   std::unique_ptr<Node> m_first;
   Node* m_last{};
};

int main()
{
   UniDirList list = {3.14, 2.71, 1.61};

   list.emplace_back(42.);

   for (double value : list)
      std::cout << value << " ";
}


Эта реализация очень груба и местами даже наивна. И вообще она не нужна, поскольку в стандартной библиотеке есть тот же std::list и другие контейнеры. Тем не менее, даже такая реализация демонстрирует возможности и преимущества C++ по всем критериям — и по простоте и наглядности кода, и по обобщенности.

Для наглядности можно посмотреть на использование:

сравни это:

int main()
{
   for (double value : UniDirList{3.14, 2.71, 1.61})
   {
      std::cout << value << " ";
   }
}


и это:

int main(int argc, char *argv[]) {
    struct INTEGER_LIST *numbers;
    numbers = new_integer_list(1, 3);
    if (!numbers) return 1;
    print_integer_list(numbers);
    free_integer_list(numbers);
    return 0;
}


Какой вариант проще для восприятия? Прими также во внимание, что я прямо в имеющейся реализации могу использовать произвольные типы данных: числа, строки, стандартные и пользовательские классы. И что пришлось бы сделать тебе, чтобы добиться сопоставимой функциональности.
--
Отредактировано 15.06.2024 11:28 rg45 . Предыдущая версия . Еще …
Отредактировано 15.06.2024 11:27 rg45 . Предыдущая версия .
Отредактировано 15.06.2024 10:39 rg45 . Предыдущая версия .
Отредактировано 15.06.2024 10:25 rg45 . Предыдущая версия .
Отредактировано 15.06.2024 10:23 rg45 . Предыдущая версия .
Отредактировано 15.06.2024 10:22 rg45 . Предыдущая версия .
Отредактировано 15.06.2024 10:19 rg45 . Предыдущая версия .
Отредактировано 15.06.2024 10:08 rg45 . Предыдущая версия .
Re: Не могу понять ссылки в C++
От: T4r4sB Россия  
Дата: 15.06.24 10:08
Оценка:
Здравствуйте, Worminator X, Вы писали:

WX>Я попытался переписать код, и вышло примерно следующее (для упрощения пока без шаблонов):


В данном случае меганаследование не нужно, а вот деструктор нужен, потому что список содержит данные которые надо удалять
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re: Не могу понять ссылки в C++
От: vsb Казахстан  
Дата: 15.06.24 10:42
Оценка: +1 :))
Ссылка это указатель, который никогда не может быть равен NULL, который нельзя изменить и который не нужно разыменовывать.
Отредактировано 15.06.2024 10:43 vsb . Предыдущая версия .
Re[2]: Не могу понять ссылки в C++
От: rg45 СССР  
Дата: 15.06.24 11:09
Оценка: 15 (2) +1
Здравствуйте, vsb, Вы писали:

vsb>Ссылка это указатель, который никогда не может быть равен NULL, который нельзя изменить и который не нужно разыменовывать.


Это достаточно поверхностное представление.

— Указатель — объект, а ссылка — нет;
— У указателя есть адрес, а у ссылки нет;
— Указатель обладает свойством двойной константности — собственная и константность объекта, у ссылок собственной константности нет;
— Ссылки на указатели возможны, а указатели на ссылки — нет;
— Также возможны указатели на указатели и даже указатели на указатели на указатели, но не бывает ссылок на ссылки;
— Бывают perfect forwarding references, но не бывает perfect forwarding pointers;
— Кроме этого ссылки обладают специальной семантикой при определении специальных функций-членов — конструкторов и операторов присваивания;
— Ссылки обладают особыми свойствами, которые могут влият на время жизни временных объектов. У указателей таких свойств нет.
--
Re: Не могу понять ссылки в C++
От: F3V  
Дата: 15.06.24 13:21
Оценка:
Здравствуйте, Worminator X, Вы писали:

WX>Несколько раз пытался освоить C++, и всегда забрасывал это дело — сложный, совершенно непонятный язык.

Забрасывал..., но ведь потом опять возвращался к C++!

WX>Что нужно исправить?

  немного код и немного пробелов в знаниях.
#include <iostream>

using namespace std;

enum ListType {
    LT_EMPTY_LIST,
    // Переименовываем LT_INTEGER_LIST в невладеющий указателями View
    LT_INTEGER_LIST_VIEW,
    // Для примера добавляем владеющий указателями Container
    LT_INTEGER_LIST_CONTAINER
};

class List {
public:
    virtual ListType getType() const = 0;
    // Добавил сервисный метод is для красоты:
    bool is(ListType type) const { return type == getType();}
    List() {}
    // Как уже подсказали, нужен деструктор при наследовании,
    // т.к. вводим владеющий контейнер:
    virtual ~List() {}
};

class EmptyList: public List {
public:
    virtual ListType getType() const override {
        return LT_EMPTY_LIST;
    }
    EmptyList(int x): List() {}
};

// Выделяем общий интерфейс:
class IntegerList: public List {
public:
    virtual int getNumber() const {
        return 0;
    }
    // чтобы не в даваться в детали, пусть будет виртуальным:
    virtual const List &getNext() const = 0;
};

// Реализация твоего IntegerView, как невладеющий указателями IntegerListView:
class IntegerListView: public IntegerList {
private:
    const int number;
    const List &next;
public:
    virtual ListType getType() const override {
        return LT_INTEGER_LIST_VIEW;
    }
    virtual int getNumber() const override {
        return number;
    }
    virtual const List &getNext() const override {
        return next;
    }
    IntegerListView(
        int number,
        const List &_next
    ): number(number), next(_next) {}
};

// Добавляем для примера владеющий указателями IntegerListContainer:
class IntegerListContainer: public IntegerList {
private:
    const int number;
    const List *next;
public:
    virtual ListType getType() const override {
        return LT_INTEGER_LIST_CONTAINER;
    }
    virtual int getNumber() const override {
        return number;
    }
    virtual const List &getNext() const override{
        return *next;
    }
    IntegerListContainer(
        int number,
        List *_next
    ): number(number), next(_next) {}
    virtual ~IntegerListContainer() override {
        if (next) {
            delete next;
        //  ^^^^^^ Поэтому и нужен виртуальный деструтор.
        }
    }
};

// Слегка меняем интерфейс функции:
void recursivePrintIntegerList(const IntegerList* pList, int counter, ListType LT_INTEGER_LIST_VIEW) {
    cout << pList->getNumber();
    if (pList->getNext().is(LT_INTEGER_LIST_VIEW)) {
        cout << ',' << ' ';
        recursivePrintIntegerList((const IntegerList*)&pList->getNext(), counter + 1, LT_INTEGER_LIST_VIEW);
    }
    if (counter == 1) {
        cout << endl;
    }
}

// Тоже слегка меняем интерфейс функции:
void recursivePrintIntegerList(const IntegerList *pList, ListType LT_INTEGER_LIST_VIEW) {
    cout << "recursivePrintIntegerList(" << LT_INTEGER_LIST_VIEW << ") => ";
    recursivePrintIntegerList(pList, 1, LT_INTEGER_LIST_VIEW);
}

// Тоже слегка меняем интерфейс функции:
void foreachPrintIntegerList(const IntegerList&list, ListType LT_INTEGER_LIST_VIEW) {
    cout << "foreachPrintIntegerList(" << LT_INTEGER_LIST_VIEW << ") => ";
    int counter = 0;
    for (
        // Тут тебе уже подсказали, что твой вариант предполагал семантику ссылок языков с GC,
        // Поэтому явное возьмём указатели:
        const List *current = &list;
        current && current->is(LT_INTEGER_LIST_VIEW);
        current = &((const IntegerList*)current)->getNext()
    ) {
        if (counter > 10) break;
        if (counter > 0) {
            cout << ',' << ' ';
        }
        counter++;
        cout << ((const IntegerList*)current)->getNumber();
    }
    cout << endl;
}

int main(int argc, char *argv[]) {
    const EmptyList empty(0);
    const IntegerListView n3(3, empty);
    const IntegerListView n2(2, n3);
    const IntegerListView n1(1, n2);

    // const IntegerList numbers(1, IntegerList(2, IntegerList(3, empty)));
    //                     Если так ^^^^^^^^^^^^ сделать,
    // то временные объекты тут же разрушатся и останутся висячие указатели на них.

    // тут можно использовать владеющий контейнер пока так:
    const IntegerListContainer numbers(1, new IntegerListContainer(2, new IntegerListContainer(3, new EmptyList(0))));
    // Кстати, после программирования на сборщике мусора,
    // людям обычно нравятся std::shared_ptr<>,
    // т.к. они по семантике похожи, но есть особенности.

    // Подправим вывод информационных сообщений:
    cout << "n1... => " << n1.getNumber() << ',' << ' ' << n2.getNumber() << ',' << ' ' << n3.getNumber() << endl;
    cout << "numbers... => " << numbers.getNumber() << ',' << ' ';
    cout << ((IntegerListContainer&)numbers.getNext()).getNumber() << ',' << ' ';
    cout << ((IntegerListContainer&)(
        (IntegerListContainer&)(
            (IntegerListContainer&)numbers.getNext()
        ).getNext()
    )).getNumber() << endl;
    recursivePrintIntegerList(&n1, n1.getType());
    recursivePrintIntegerList(&numbers, numbers.getType());
    foreachPrintIntegerList(n1, n1.getType());
    foreachPrintIntegerList(numbers, numbers.getType());
    return 0;
}


n1... => 1, 2, 3
numbers... => 1, 2, 3
recursivePrintIntegerList(1) => 1, 2, 3
recursivePrintIntegerList(2) => 1, 2, 3
foreachPrintIntegerList(1) => 1, 2, 3
foreachPrintIntegerList(2) => 1, 2, 3

WX>что такое есть ссылка в C++ (вроде синтаксический сахар над указателем, но как-то странно и непонятно работает).
Это ты ещё думаешь семантикой языков с GC. Надо уже переключиться на C++.
Есть разница в понятии "ссылка" в языках со сборщиком мусора и без него:
В языках со сборщиком мусора ссылка может изменять адрес связанного объекта.
В С++ ссылка не может изменять адрес связанного объекта.
Re[2]: Не могу понять ссылки в C++
От: m2user  
Дата: 15.06.24 14:12
Оценка:
WX>>Что нужно исправить?

Зачем вообще рекурсия при обходе связного списка?
Re[2]: Не могу понять ссылки в C++
От: Worminator X Россия #StandWithPalestine 🖤🤍💚
Дата: 15.06.24 14:40
Оценка:
Здравствуйте, andrey.desman, Вы писали:

AD>Ссылки — это не совсемсинтаксический сахар над указателями. По крайней мере не в смысле определения, хотя их и можно использовать как таковые. Ссылка — это альтернативное имя для какого-то выражения, алиас.

AD>Саму ссылку нельзя изменить, она привязывается (bind) к чему-то в момент определения и остается привязанной к этому выражению до конца своего существования.
AD>Грубо говоря, ссылка ведет себя как разыменованный константный указатель. Не указатель на константу, а именно константный указатель. А, например, константная ссылка как константный указатель на константу (const * const).

AD>создается ссылка current, которая привязывается к list. Она и останется привязанной к list.

AD>Выражение current = ((IntegerList&)current).getNext() не перепривязывает ссылку current к другому объекту, а присваивает привязанному объекту list значение из list.getNext().

AD>Как раз здесь ссылки неуместны, если ты конечно не строишь иммутабельные списки. Если потребуется список поменять, его придется перестроить весь, от начала и до конца.

AD>При этом, с динамической памятью на ссылках работать не получится.

Мда, сложно как-то. Ну его на фиг, этот C++, лучше чистый Си с указателями и Java для ООП.
А списки хотел сделать именно иммутабельными (вообще это попытка написать свой интерпретатор Лиспа, там было более сложное наследование с атомами и списочными парами).
Ссылка — это некий аналог #define макросов для компилятора, получается?
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Отредактировано 15.06.2024 14:57 Worminator X . Предыдущая версия .
Re[3]: Не могу понять ссылки в C++
От: Worminator X Россия #StandWithPalestine 🖤🤍💚
Дата: 15.06.24 14:41
Оценка:
Здравствуйте, m2user, Вы писали:

M>Зачем вообще рекурсия при обходе связного списка?


Потому что цикл for на ссылках написать не смог. Как здесь объяснили, это потому что ссылку поменять нельзя.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Отредактировано 15.06.2024 14:48 Worminator X . Предыдущая версия .
Re[2]: Не могу понять ссылки в C++
От: Worminator X Россия #StandWithPalestine 🖤🤍💚
Дата: 15.06.24 14:46
Оценка:
Здравствуйте, F3V, Вы писали:

Для чего тогда вообще в C++ нужны ссылки? Какую задачу они решают? Если все равно приходится использовать указатели, то достаточно моего 2-го кода с new/delete.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Re[3]: Не могу понять ссылки в C++
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 15.06.24 14:52
Оценка:
Здравствуйте, Worminator X, Вы писали:

WX>Ну его на фиг, этот C++, лучше чистый Си с указателями


Если не нравятся специфические приемы C++ — пишите на "C с классами", используя где указатели, где ссылки, в зависимости от удобства.
Re[2]: Не могу понять ссылки в C++
От: Worminator X Россия #StandWithPalestine 🖤🤍💚
Дата: 15.06.24 14:56
Оценка:
Здравствуйте, rg45, Вы писали:

R>Какой вариант проще для восприятия? Прими также во внимание, что я прямо в имеющейся реализации могу использовать произвольные типы данных: числа, строки, стандартные и пользовательские классы. И что пришлось бы сделать тебе, чтобы добиться сопоставимой функциональности.


Для меня пока код на STL и Boost выглядит как магия. Примерно как Spring Boot для начинающих Java разработчиков, не понимающих устройство аннотаций и инициализацию application context'а при старте.
Разработчик на C++ не должен воспринимать код как магию, он должен полностью понимать внутреннее устройство программы, и в какие инструкции она компилируется.
Поэтому изучению STL и других библиотек должно предшествовать полное изучение языковых возможностей, об этом же пишет доцент Столяров в своих книгах.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Re[3]: Не могу понять ссылки в C++
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 15.06.24 14:58
Оценка: 1 (1)
Здравствуйте, Worminator X, Вы писали:

WX>Для чего тогда вообще в C++ нужны ссылки? Какую задачу они решают?


Ссылки позволяют представить объекты в едином виде, без разделения на статические, автоматические и динамические (а в C++ могут быть еще и временные).

WX>Если все равно приходится использовать указатели


Нет смысла избавляться от указателей просто потому, что они указатели. Там, где указатель имеет смысл, как независимый объект (например, где он перемещается по массиву/списку, меняет значение произвольно и т.п.), логично использовать именно указатель. А в функцию, которая обрабатывает просто абстрактный объект, имеет смысл передавать именно ссылку. Если передавать указатель, в функции возникает совершенно ненужная сущность и возможность ею оперировать.
Re[3]: Не могу понять ссылки в C++
От: rg45 СССР  
Дата: 15.06.24 15:00
Оценка: 2 (1) +1
Здравствуйте, Worminator X, Вы писали:

WX>Для меня пока код на STL и Boost выглядит как магия. Примерно как Spring Boot для начинающих Java разработчиков, не понимающих устройство аннотаций и инициализацию application context'а при старте.

WX>Разработчик на C++ не должен воспринимать код как магию, он должен полностью понимать внутреннее устройство программы, и в какие инструкции она компилируется.
WX>Поэтому изучению STL и других библиотек должно предшествовать полное изучение языковых возможностей, об этом же пишет доцент Столяров в своих книгах.

Ну, все правильно, в общем-то. Тут все зависит от личной мотивации и желания. Остальное — лишь дело техники. Ну и практика, куда ж без нее.
--
Re[4]: Не могу понять ссылки в C++
От: Worminator X Россия #StandWithPalestine 🖤🤍💚
Дата: 15.06.24 15:02
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Если не нравятся специфические приемы C++ — пишите на "C с классами", используя где указатели, где ссылки, в зависимости от удобства.


Для себя я пишу на чистом Си без виртуальных методов. В принципе связка Java + Си через JNI + ассемблер для платформенно-зависимых вещей решает практически любые задачи.
Просто бытует мнение, что без знания C++ и Haskell нельзя считаться настоящим программистом.
И в геймдеве, например, многие библиотеки только под C++, чистый Си там почему-то не любят.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Re[3]: Не могу понять ссылки в C++
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 15.06.24 15:05
Оценка:
Здравствуйте, Worminator X, Вы писали:

WX>Для меня пока код на STL и Boost выглядит как магия.


Так это магия и есть, причем весьма корявая. Кто-то считает, что это и есть "истинный C++", кто-то так не считает.

WX>Разработчик на C++ не должен воспринимать код как магию


Не должен, но уже пара поколений C++-активистов его к этому принуждают.

WX>он должен полностью понимать внутреннее устройство программы, и в какие инструкции она компилируется.


По факту, это уже давно считается немодным. Теперь и на C++ программирование заключается в "кодировании алгоритма конструкциями языка".

WX>Поэтому изучению STL и других библиотек должно предшествовать полное изучение языковых возможностей


А толку? Чисто языковые возможности C++ и шаблонная магия практически ортогональны друг другу. Более того, после вдумчивого изучения и использования "базового" C++, шаблонная магия способна вызывать стойкое отвращение.
Re[4]: Не могу понять ссылки в C++
От: Worminator X Россия #StandWithPalestine 🖤🤍💚
Дата: 15.06.24 15:05
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Нет смысла избавляться от указателей просто потому, что они указатели. Там, где указатель имеет смысл, как независимый объект (например, где он перемещается по массиву/списку, меняет значение произвольно и т.п.), логично использовать именно указатель. А в функцию, которая обрабатывает просто абстрактный объект, имеет смысл передавать именно ссылку. Если передавать указатель, в функции возникает совершенно ненужная сущность и возможность ею оперировать.


Так, то есть указатели нужны там, где связанный объект может меняться (при обходе по циклу, например)? А ссылки всегда иммутабельные после первого присваивания?
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Re[5]: Не могу понять ссылки в C++
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 15.06.24 15:17
Оценка: +1
Здравствуйте, Worminator X, Вы писали:

WX>Для себя я пишу на чистом Си без виртуальных методов.


Если будете писать на "C с классами и виртуальными методами", то на выходе получите почти (с точностью до особенностей оптимизатора) идентичный код.

WX>бытует мнение, что без знания C++ и Haskell нельзя считаться настоящим программистом.


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

WX>И в геймдеве, например, многие библиотеки только под C++, чистый Си там почему-то не любят.


Я вообще не вижу ни одного преимущества чистого C перед C++, кроме некоторого синтаксического сахара, добавленного в C в процессе параллельного развития с C++. Все, что написано на C, можно переписать на C++, и это будет более компактно, более понятно, более надежно и не менее эффективно. Но вот это откровенно дурацкое убеждение "не используя шаблонную магию, вы не используете C++" настолько прочно засрало всем мозги, что изучение C++ часто начинают именно с нее. Адекватно понять эти нечеловеческие конструкции без глубокого понимания ядра языка невозможно, поэтому большинство их тупо заучивает, и применяет где по шаблону, где по аналогии, а где просто интуитивно.

А если не стремиться следовать модным трендам, и использовать C++ в том объеме, который реально необходим для задачи, он у C только выигрывает.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.