Klasa macierzy.

0

Witam, mam pewne pytanie związane z konstrukcją klasy macierzy rzeczywistych, którą napisaliśmy na zajęciach z programowania - dotyczy to głównie składni języka C++.

Fragmenty kodu:

#include <iostream.h>
#include<stdio.h>
#include<conio.h>
#include<math.h>

class macierz
 {
private:
	int n,m;
	int tymcz;
	usun_tymcz();
	double **t;
public:
	macierz(int,int);
	macierz();
	~macierz();
	macierz& operator+(macierz&);
	macierz& operator=(macierz&);
	friend istream& operator >> (istream&,macierz&);
	friend ostream& operator << (ostream&,macierz);
};

macierz::macierz(int a, int b)
{
n=a;
m=b;
t=new double*[n];
for(int i =0 ; i<n;i++)
 t[i]=new double[m];
for (int i=0;i<n;i++)
 for(int j=0; j<m;j++)
  t[i][j]=0;
}

macierz::macierz()
{
tymcz==0;
n=3;
m=3;
t=new double*[n];
for(int i =0 ; i<n;i++)
 t[i]=new double[m];
for (int i=0;i<n;i++)
 for(int j=0; j<m;j++)
  t[i][j]=0;
}



macierz ::~macierz()
{
 if(tymcz==0)
 {
  for(int i=0;i<n;i++)
  delete t[i];
  delete t;
 }
}

macierz::usun_tymcz()
{
 if(tymcz==1)
 {
 for(int i=0;i<n;i++)
  delete t[i];
 delete t;
 }
}

macierz& macierz:: operator+(macierz& a)
{
macierz *w=new macierz(n,m); 
w->tymcz=1;
if (n==a.n && m==a.m)
 {
 for(int i=0;i<n;i++)
 for(int j=0;j<m;j++)
 w->t[i][j]= t[i][j]+ a.t[i][j];
 a.usun_tymcz();
 }
else
 {
 cout<<"Nie mozna dodac macierzy";
 }
usun_tymcz();
return *w;
}


macierz& macierz:: operator=(macierz& a)
{
for(int i=0;i<a.n;i++)
for(int j=0;j<a.m;j++)
t[i][j]=a.t[i][j];
a.usun_tymcz();
return *this;
}


istream& operator >> (istream& in , macierz& Y)
{
for(int i=0;i<Y.n;i++)
 for(int j=0;j<Y.m;j++)
  in >> Y.t[i][j] ;
return in;
}

ostream& operator << (ostream& out,macierz Y)
{
for(int i=0; i<Y.n; i++)
 {
  for(int j=0; j<Y.m; j++)
   {
    out << Y.t[i][j] << " "; 
   }
 out << endl; 
 }
return out;
}


Zamieściłam tutaj tylko operacje pobrania oraz wypisania elementów macierzy, dodania oraz przypisania. Moje pytanie jest następujące: dlaczego koniecznie jest przesyłanie argumentów przez referencję? (np. w dodawaniu macierzy) Bez tego niestety program nie działa.

0

Po pierwsze zamień "iostream.h" na "iostream". "iostream.h" jest starą wersją biblioteki strumieniów która jest jeszcze sprzed standardu z 98 roku. Program nie działa poprawnie , ponieważ jak byś chciał przesłać obiekt macierzy do funkcji przez wartość to wewnątrz tej funkcji jest wywoływana kolejna funckja usun_tymcz która jest wywoływana na rzecz lokalnej kopii obiektu klasy macierzy. Czyli tak de facto nic się by nie usunęło z obiektu który przesłalibyśmy do funkcji. To są oczywiście tylko domysły bo nie widzę całego kodu.

0

Referencja -> przekazujesz "prawdziwą zmienną"
bez referencji -> przekazujesz KOPIE zmiennej/obiektu

polecam poczytać http://pl.wikibooks.org/wiki/C++/Referencje
"Przekazywanie argumentów przez referencję"

0
fasadin napisał(a):

Referencja -> przekazujesz "prawdziwą zmienną"
bez referencji -> przekazujesz KOPIE zmiennej/obiektu

To akurat wiem - ale nie wiem, dlaczego trzeba akurat w tym przypadku pracować na oryginale, a nie na kopii.

Funkcja usun_tymcz też za coś odpowiada, z tego co zrozumiałam, chodzi o to, by "ręcznie" uruchamiać destruktor. Normalnie usuwa on lokalną kopię obiektu w funkcji, ale ponieważ kopiujemy tu macierz dynamiczną, której jednym z parametrów jest wskaźnik, to usunięcie wskaźnika powoduje usunięcie tego, co jest pod jego adresem, a więc w rezultacie oryginału. Więc nie usuwamy destruktora od razu, tylko gdzieś tam później (właściwie też nie wiem gdzie). Tylko po co ta referencja? :/

0

Wywal tymcz i wszystko co z nim związane i zrób normalny konstruktor kopiujący.
Poza tym zastąp int na unsigned lub size_t.

0

Bez tymcz nie działa. Wywala jakieś błędy.
Będę pisać kolokwium, gdzie muszę wykazać się znajomością tematu, co, jak, po co i dlaczego jest w tym a nie innym miejscu - więc modyfikacja programu niczego mi nie ułatwia...

0

OK, już dwukrotnie w tym temacie pokazaliście mi, że jestem lamerem - dzięki za powitanie na nowym forum! - jeszcze coś zrobiłam źle?
"moje marne próby", to nie moje, tylko prowadzącego zajęcia - miał widać powód, żeby tak pisać, nie wiem niestety dlaczego i nie wiem też, czym jest konstruktor kopiujący.

0

nie, nie było to trudne, jednakże nadal nic nie wnosi to do tematu - po pierwsze, niewiele z tego rozumiem (dopiero zaczynam programowanie), po drugie, wciąż nie dostałam odpowiedzi na pierwotne pytanie.
mniejsza o to, jakoś sobie poradzę - temat do zamknięcia.

0

Jeśli ci chodzi o to czemu tam jest referencja, a nie orginał - to spójrz co to robi. Dodajesz dwie macierze. W kodzie dodawania masz usun_tymcz. Jest to wywoływane, na elemencie do którego dodajesz i który dodajesz. Jakbyś wywołał to bez referencji, to po dodawaniu element dodany wciąż by miał jakieś tam wartości (które coś tam robią, nie chce mi się patrzeć co konkretnie usuwa usun_tymcz). Ponieważ przekazujesz 'oryginał' - wartości tymczasowe się usuwają. W skrócie chodzi o to że autor chciał zmodyfikować obiekt, który jest dodawany. I dlatego musisz zrobić to przez referencje.

1
katta1992 napisał(a):

czym jest konstruktor kopiujący.
ogólnie zasada jest taka, którą powinnaś zapamiętać i się tego trzymać (przynajmniej jeśli jeszcze nie wszystko robisz świadomie) - gdy twoja klasa przechowuje jakiś wskaźnik "z automatu" powinnaś zdefiniować w klasie "świętą trójcę" tzn.

  • konstruktor kopiujący
  • operator przypisania
  • destruktor

ponadto jeśli klasa ma być klasą bazową dla innych klas destruktor powinien być destruktorem wirtualnym.

Konstruktor kopiujący rodzaj konstruktora, który przyjmuje jako parametr referencję (powinien stałą referencję chociaż są wyjątki, których powinno się unikać) na obiekt klasy, której definiujesz ten konstruktor. Wywoływany jest w różnych przypadkach gdy jest wykonywana kopia twojego oryginalnego obiektu inicjalizowana nim, czyli na przykład w sytuacjach:

  • przy jawnym wywołaniu konstruktora kopiującego podczas inicjalizacji [1]
  • przy przekazywaniu parametrów do funkcji przez wartość [2]
  • przy korzystaniu z kontenerów stl [3]

Zdefiniowanie tego konstruktora w przypadku klasy ze wskaźnikiem jest niezbędne ponieważ domyślnie wygenerowany przez kompilator konstruktor kopiujący, jak sama intuicja podpowiada kopiuje pola jednej klasy do drugiej. Niestety w przypadku wskaźników samo skopiowanie wskaźnika nie jest tym czego ty chcesz ponieważ sama operacja kopiowania powoduje iż powstaje druga zmienna wskaźnikowa (w skopiowanym obiekcie), która wskazuje na to samo miejsce w pamięci, tzn.
Masz obiekt 'obj1' z jakimś wskaźnikiem, oraz kopie 'obj2' obiektu 'obj1', która zawiera wskaźnik na to samo miejsce w pamięci co wskaźnik z 'obj1'. Teraz załóżmy, że któryś z obiektów niszczysz, nie ważne który. Tutaj pojawia się problem ponieważ po zniszczeniu jednego z tych obiektów korzystanie z drugiego obiektu staje się niebezpieczne bo przechowuje on wskaźnik, który wskazuje na zwolnioną pamięć (pamięć ta została zwolniona przez destruktor jednego z tych obiektów - tego, który został usunięty). Masz najzwyczajniej w świecie wyciek pamięci. Żeby się przed tym uchronić nie możesz kopiować wskaźnika, tylko jego wartość to znaczy:

  1. nowym obiekcie musisz przydzielić nowe miejsce w pamięci
  2. miejsce to wypełnić kopią wartości wskaźnika obiektu, z którego kopiujesz
    To powinien robić konstruktor kopiujący w twoim wypadku. Przykład poniżej.

Operator przypisania
Operator przypisania musi się znaleźć z podobnego względu jak konstruktor kopiujący tylko zostaje on wywołany w innym przypadku - jak intuicja podpowiada podczas przypisania (pokazane w przykładzie poniżej).

Destruktor
Jeśli w konstruktorach przydzielasz pamięć (np przy pomocy operatora new) musisz ją gdzieś zwolnić. Czynisz to w destruktorze. Dlatego powinien się on tam znaleźć. O destruktorze wirtualnym nie będę ci w tej chwili pisał bo pomimo iż jest to równie proste to może jednak na tym etapie ci troszkę namieszać. Przyjdzie to z czasem. Na razie musisz wiedzieć to co napisałem.

przykład:

class A {
    int *x;
    public:
        A(int x_) : x(new int(x_)) { cout << "konstruktor A()\r\n"; }

        A(const A &a) : x(new int(*a.x))
        {
            cout << "konstruktor kopiujacy A(const A&) " << "\r\n";
        }

        ~A()
        {
            if(x)
                delete x;

            cout << "destruktor ~A()\r\n";
        }

        A& operator= (const A &a)
        {
            if(&a != this) //na wypadek gdyby: obj = obj
            {
                if(x)
                    delete x;
                x = new int(*a.x);
            }
            cout << "Operator przypisania operator=(const A&)\r\n";
            return *this;
        }

        void wypiszX() const { cout << "x: " << *x << "\r\n"; }
};

void foo(A obj) {}

int main()
{
    A obj1(20);
    obj1.wypiszX();
    A obj2(obj1); //"jawne" wywolanie konstruktora kopiujacego [1]
    obj2.wypiszX();

    cout << "przy wejsciu do foo: ";
    foo(obj1); //przekazywanie przez wartosc [2]

    A obj3(30);
    obj3.wypiszX();
    obj3 = obj1; // przypisanie
    obj3.wypiszX();

    vector<A> v;
    v.push_back(obj1); //przy kontenerach stl [3]
}

Teraz nasuwają się pytania:

  • dlaczego konstruktor kopiujący przyjmuje referencje (stałą) do obiektu, a nie przekazuje parametru przez wartość? - ponieważ przy przekazaniu przez wartość wywoływany jest konstruktor kopiujący czyli przy definiowaniu konstruktora kopiującego musiałabyś wykorzystać ... konstruktor kopiujący - głupie nie? I bezsensowne, dlatego jest tam referencja.

Dlaczego referencja znajduje się przy twoich operatorach? Pomijając już to, że wewnątrz tych operatorów wykonujesz operacje wpływające na stan obiektu, który przekazujesz (fuj :/ - wywołując ten operator nie do końca liczę na to iż mój argument ulegnie jakiejkolwiek zmianie) to spójrz na to z tej strony - operacje te mogą być wywoływane wielokrotnie podczas korzystania z twojej klasy. Tworzenie za każdym razem kopii obiektu, który się przekazuje nie jest dobrym rozwiązaniem - jest pamięciożerne i czasochłonne - po co to robić skoro można przekazać oryginał ?

Jeszcze mała uwaga:

katta1992 napisał(a):

Witam, mam pewne pytanie związane z konstrukcją klasy macierzy rzeczywistych, którą napisaliśmy na zajęciach z programowania - dotyczy to głównie składni języka C++.

Fragmenty kodu:

macierz::macierz()
{
tymcz==0;
n=3;
m=3;
t=new double*[n];
for(int i =0 ; i<n;i++)
 t[i]=new double[m];
for (int i=0;i<n;i++)
 for(int j=0; j<m;j++)
  t[i][j]=0;
}

tymcz == 0 ?

oraz to zerowanie - możesz wykorzystać to iż w pierwszej pętli (tej z new) przelatujesz bo wierszach.
Dodatkowo:

macierz ::~macierz()
{
 if(tymcz==0)
 {
  for(int i=0;i<n;i++)
  delete t[i];
  delete t;
 }
}

tutaj jest wywoływany zła wersja operatora delete (delete []).

Na koniec - nie powinnaś się unosić z powodu odpowiedzi, które ci udzielono ponieważ userzy mają rację - pytasz o totalne podstawy, nie stosujesz się do regulaminu działu (o netykiecie nie wspomnę) jednak kod źródłowy oraz staż Cie ratują. Przyjmij krytykę z pokorą i wyciągnij naukę z tego, nikt tutaj ci nie złorzeczy ani nie chce zaszkodzić. Więcej własnego wkładu oraz samodzielnych zmagań z poszukiwaniami, przeglądanie forów, czytanie dokumentacji rozwiną nie tylko twoje umiejętności programistyczne ale również nauczą skutecznego wyszukiwania informacji. Zadanie pytania na forum jest najłatwiejszym rozwiązaniem - najmniej uczącym i najbardziej czasochłonnym - uwierz mi, z mojego postu nawet jeśli coś wyniesiesz to zapomnisz większość bo sama do tego nie doszłaś.

pozdrawiam.

0

Dziękuję za wyjaśnienie zagadnienia i wskazanie błędów w kodzie.
W kwestii samej dyskusji - owszem, pytam o podstawy - ale przecież po to jest dział Newbie, prawda? Odpowiedzi na to pytanie szukam od tygodnia w różnych źródłach - w książkach, w internecie, pytam znajomych - jednak poza samą definicją referencji niewiele znalazłam. Dlatego zdecydowałam się na zamieszczenie pytania na forum.
Jeżeli tagowanie wątku jest konieczne, dlaczego nie widnieje jako obowiązkowe podczas pisania nowego tematu? To chyba ułatwiłoby wielu nowym użytkownikom pamiętanie o regulaminie.
Przykro mi, że tak potoczyła się dyskusja, ale nie uważam, aby było to konsekwencją jedynie moich wypowiedzi.

1 użytkowników online, w tym zalogowanych: 0, gości: 1