Не могу понять ссылки в 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) не помог.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.