• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Przeciążanie [] tak, aby arr1D[][] było możliwe

Aruba Cloud VPS - 50% taniej przez 3 miesiące!
0 głosów
239 wizyt
pytanie zadane 30 grudnia 2022 w C i C++ przez Krzysztofs1234 Użytkownik (890 p.)

Dzień dobry,

starałem się cel ująć w tytule, niemniej należy tutaj dokładnie opisać, że mam na myśli rzecz następującą:

Mam (dynamiczną) tablicę jednowymiarową i chcę traktować ją jako dwuwymiarową, zatem chciałbym móc na niej używać podwójnego operatora "[]", gdzie pierwszy naturalnie wskazywałby wiersz, a drugi kolumnę.

Doszedłem do tego:

#include <iostream>
using namespace std;


class Matrix
{
    friend class MatrixRow;


private:
    double* matrix;
    unsigned rows;
    unsigned columns;

    unsigned rowIdx=0;


    class MatrixRow
	{
	    friend class Matrix;

    private:
		double operator[](unsigned i)
		{
			return Matrix::matrix[Matrix::rowIdx+i];
		}
	};


public:
	Matrix(unsigned rows, unsigned columns)
	{
	    this->rows=rows;
	    this->columns=columns;
	    matrix=new double[rows*columns];

	    for(unsigned i=0; i<rows*columns; i++)
        {
            matrix[i]=static_cast<double>(i)+0.1;
        }
	}

	void operator[](unsigned i) const
    {
        rowIdx=i;
        MatrixRow::operator[]()
    }

	void operator=(double* matrix)
	{
		this->matrix=matrix;
	}
};



int main()
{
	Matrix matrix(3, 4);

	cout << matrix[2][3];

    return 0;
}

W pierwszym operatorze [], którego przeciążenie obsługuje sama klasa Matrix, muszę przekazać indeks wiersza. Mogę sprawić, że klasa wewnętrzna, MatrixRow, będzie znała ten indeks poprzez zaprzyjaźnienie klas i dodatkowe pole RowIdx. Natomiast nie wiem, co z wywołaniem przeciążenia operatora w klasie MatrixRow. Co ja mam przekazać jako argument? Nie wiem, może już od początku to jest zły.

Proszę o wskazówki.

2 odpowiedzi

+2 głosów
odpowiedź 30 grudnia 2022 przez mokrowski Mędrzec (156,320 p.)
wybrane 30 grudnia 2022 przez Krzysztofs1234
 
Najlepsza

IMHO w stosunku do wymagań, przekombinowałeś.

BTW, nie poruszam kwestii sposobu implementacji ani wybranych środków...

#include <iostream>
#include <cstddef>

using namespace std;
 
class Matrix
{
private:
    double * matrix;
    const size_t rows;
    const size_t columns;
public:
    Matrix(size_t rows_, size_t columns_)
    	    : rows{rows_}, columns{columns_}, matrix{new double[rows_ * columns_]}
    {
    	    // TODO: Check matrix != nullptr
    	    for (size_t i = 0; i < (rows * columns); ++i) {
    	    	    matrix[i] = 0.1 + i;
    	    }
    }
    ~Matrix() {
    	    delete [] matrix;
    }
 
    double * operator[](size_t row) const
    {
    	    return (matrix + row * columns);
    }
};
 
int main()
{
    Matrix matrix(3, 4);
 
    cout << matrix[2][3] << '\n';

    matrix[2][3] = 1001.1;

    cout << matrix[2][3] << '\n';
 
    return 0;
}

 

komentarz 30 grudnia 2022 przez Krzysztofs1234 Użytkownik (890 p.)
Super. Co więcej, działa także przypisywanie, a z początku myślałem, że będzie to jedynie odczyt, a do przypisania trzeba będzie użyć innego przeciążenia z "&".

Nie bardzo jednak rozumiem, jak to się dzieje, że z jednym [] wyświetla się adres komórki, a z dwoma [][] już wartość. Tak samo przypisanie wymaga dwóch takich operatorów, z jednym się nie da.

Byłbyś uprzejmy wyjaśnić, co się dzieje krok po kroku?
komentarz 30 grudnia 2022 przez mokrowski Mędrzec (156,320 p.)

Owszem, o ile istnieje w klasie operator[], to będzie użyty w przypadku wywołania z nawiasami kwadratowymi. Jeśli jednak nie istnieje, warto wiedzieć że nawiasy kwadratowe są tylko lukrem składniowym na dostęp do adresu.

#include <iostream>

int main() {
	int tab[] = {10, 20, 30};
	// Takie wywołanie...
	std::cout << tab[1] << '\n';
	// ... to to samo co...
	std::cout << *(tab + 1) << '\n';
	// ... z racji przemienności dodawania to samo co...
	std::cout << *(1 + tab) << '\n';
	// Jeśli więc tab[1] to to samo co *(1 + tab) to...
	std::cout << 1[tab] << '\n';
}

Stąd też jeśli zwrócimy z operatora operator[] adres, następne nawiasy kwadratowe będą operowały na konkretnym bajcie poprzez arytmetykę adresu :)

komentarz 30 grudnia 2022 przez Krzysztofs1234 Użytkownik (890 p.)

Dalej jednak nie mogę pojąć, że funkcja (mam na myśli to przeciążenie []), zwracająca jedynie double*, raz zwraca adres, a za drugim już wartość. Dlaczego nie za obydwoma razami wartości albo adresu?

Co więcej, odkryłem, że pomimo podwójnego operatora [], funkcja jest wywoływana tylko raz, a przynajmniej takie mam wrażenie.

#include <iostream>
#include <cstddef>

using namespace std;

class Matrix
{
private:
    double* matrix;
    size_t rows;
    size_t columns;

public:
    Matrix(unsigned rows, unsigned columns)
    {
        this->rows=rows;
        this->columns=columns;
        matrix=new double[rows*columns];

        for(unsigned i=0; i<rows*columns; i++)
        {
            matrix[i]=static_cast<double>(i)+1.1;
        }
    }

    ~Matrix()
    {
        delete [] matrix;
    }

    double* operator[](size_t row) const
    {
        cout << row*columns << "       ";
        return (matrix + row * columns);
    }
};

int main()
{
    Matrix matrix(3, 4);

    unsigned i, j;
    for(i=0; i<3; i++)
    {
        for(j=0; j<4; j++)
        {
            cout << "matrix[" << i << "][" << j << "]: " << matrix[i][j] << endl;
        }
    }

    matrix[3][1]=801.108;
    cout << endl << endl;

    for(i=0; i<3; i++)
    {
        for(j=0; j<4; j++)
        {
            cout << "matrix[" << i << "][" << j << "]: " << matrix[i][j] << endl;
        }
    }


    return 0;
}

 

Poza tym skąd program wie, że za pierwszym razem podawane są wiersze, a za drugim kolumny. Zwracając przecież:

return (matrix + row * columns);

Za pierwszym razem mam: matrix + <podany wiersz> * columns

a za drugim: matrix + <podana kolumna> * columns

co w konsekwencji daje przesunięcie: matrx + <podany wiersz> * columns + <podana kolumna>*columns

a moim zdaniem powinno być: matrx + <podany wiersz> + <podana kolumna>

komentarz 30 grudnia 2022 przez mokrowski Mędrzec (156,320 p.)

Poczytaj trochę o sposobie ułożenia tablic 2-wymiarowych w pamięci. Zrozumiesz z jakiego powodu operuję taką arytmetyką. Wiersze układane są w pamięci "co ilość kolumn" czyli (małe ASCII Art):

tab[3][3];

a b c
d e f
g h i

W pamięci:
 a b c d e f g h i
|                      
niskie adresy  
                  wysokie adresy

Tak. Przeciążona metoda operator[](...), wywoływana jest tylko dla wiersza. Kolumna trafia już jako arytmetyka adresów (stąd nie ma komunikatów z nią związanych).

Oczywiście  możesz chcieć mieć "obiekt owijający dla wiersza". Tylko należy mieć poważny powód (np. kontrola zakresów podawanych indeksów i reakcja na przekroczenie zakresu).

Proponuję mały eksperyment. Zaadresuj 2 wiersz (czyli indeks 1 bo liczymy od zera) i kolumnę większą o 1 od maksymalnej. Zobacz którą wartość zwróci program (podpowiem że 1 czyli z indeksem 0 z następnego wiersza).

komentarz 30 grudnia 2022 przez Krzysztofs1234 Użytkownik (890 p.)

 Poczytaj trochę o sposobie ułożenia tablic 2-wymiarowych w pamięci. Zrozumiesz z jakiego powodu operuję taką arytmetyką. Wiersze układane są w pamięci "co ilość kolumn" czyli (małe ASCII Art):

Nie omieszkam, choć przewiduję, że jedną z konkluzji może być: "pamięć jest płaska". 

a moim zdaniem powinno być: matrx + <podany wiersz> + <podana kolumna>

Znowu się pomyliłem. Oczywiście tam <podany wiersz> powinno być domnożone przez liczbę kolumn.

 

 Kolumna trafia już jako arytmetyka adresów (stąd nie ma komunikatów z nią związanych).

 Nie umiem z tego wywnioskować, co się dzieje, gdy używam tych operatorów do przypisania czy odczytania. Dlaczego tą drogą nie mogą użyć potrójnego operatora"[]", choćby były tam umieszczone same zera?

Oczywiście  możesz chcieć mieć "obiekt owijający dla wiersza". Tylko należy mieć poważny powód (np. kontrola zakresów podawanych indeksów i reakcja na przekroczenie zakresu).

Dokładnie taki jest powód. Program skapituluje przy próbie uzyskania wartości spoza zakresu, więc trzeba to kontrolować. Również wolałbym, aby użytkownik nie mógł także...

Proponuję mały eksperyment. Zaadresuj 2 wiersz (czyli indeks 1 bo liczymy od zera) i kolumnę większą o 1 od maksymalnej. Zobacz którą wartość zwróci program (podpowiem że 1 czyli z indeksem 0 z następnego wiersza).

A odnosząc się do cytatu, robiłem tak uprzednio, powiedzmy, za kulisami. "Pamięć jest płaska" ciśnie się na usta. :D

0 głosów
odpowiedź 30 grudnia 2022 przez Great Stary wyjadacz (12,660 p.)
edycja 30 grudnia 2022 przez Great

MatrixRow powinien znać obiekt - możesz przekazać wskaźnik w konstruktorze MatrixRow do macierzy. Operator [] zwracałby obiekt klasy MatrixRow. Prostsze rozwiązanie to przeciążenie operatora():

class Matrix {
    // ...

    double operator()(unsigned row, unsigned col) {
        return matrix[row * columns + col];
    }
};

int main() {
    Matrix matrix(3, 4);
    cout << matrix(2, 3);
}
komentarz 30 grudnia 2022 przez Krzysztofs1234 Użytkownik (890 p.)

Owszem, z operatorem () jest znacznie prościej.

Natomiast chciałbym też opanować wersję z podwójnymi nawiasami kwadratowymi.

 

MatrixRow powinien znać obiekt - możesz przekazać wskaźnik w konstruktorze MatrixRow do macierzy. Operator [] zwracałby obiekt klasy MatrixRow.

 Nie rozumiem. MatrixRow zdaje mi się, że zna obiekt klasy Matrix, bo używam Matrix:: i klasy są zaprzyjaźnione. Nie rozumiem też, o co chodzi ze wskaźnikiem ani ze zwracaniem obiektu klasy MatrixRow.

komentarz 30 grudnia 2022 przez Great Stary wyjadacz (12,660 p.)

Nie rozumiem. MatrixRow zdaje mi się, że zna obiekt klasy Matrix, bo używam Matrix:: i klasy są zaprzyjaźnione.

Deklaracja przyjaźni do deklaracja umożliwiająca dostęp do prywatnych i chronionych pól. Niestatyczne odwołanie do składowej musi być określone względem konkretnego obiektu.

Podobne pytania

0 głosów
1 odpowiedź 115 wizyt
0 głosów
1 odpowiedź 363 wizyt
0 głosów
1 odpowiedź 603 wizyt

93,174 zapytań

142,185 odpowiedzi

321,976 komentarzy

62,503 pasjonatów

Advent of Code 2024

Top 15 użytkowników

  1. 1389p. - dia-Chann
  2. 1368p. - Łukasz Piwowar
  3. 1360p. - Łukasz Eckert
  4. 1360p. - CC PL
  5. 1344p. - Tomasz Bielak
  6. 1117p. - ssynowiec
  7. 1104p. - Michal Drewniak
  8. 1083p. - Marcin Putra
  9. 1078p. - rucin93
  10. 1071p. - rafalszastok
  11. 1054p. - Adrian Wieprzkowicz
  12. 1047p. - Piotr Aleksandrowicz
  13. 1037p. - Michał Telesz
  14. 1023p. - Mariusz Fornal
  15. 1017p. - Mikbac
Szczegóły i pełne wyniki

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj

Wprowadzenie do ITsec, tom 1 Wprowadzenie do ITsec, tom 2

Można już zamawiać dwa tomy książek o ITsec pt. "Wprowadzenie do bezpieczeństwa IT" - mamy dla Was kod: pasja (użyjcie go w koszyku), dzięki któremu uzyskamy aż 15% zniżki! Dziękujemy ekipie Sekuraka za fajny rabat dla naszej Społeczności!

...