Klasa Array ( dynamiczne tablice )

0

Mam zrobić projekt na zaliczenie z C++ i ma to być klasa Array reprezentująca dynamiczne tablice. No i zaciąłem się na przeciążeniu operatora <<. Sypie błędami aż miło, a nie mam pojęcia dlaczego. Jakby ktoś rzucił okiem byłbym wdzięczny.

Klasa:

template<class Element>
class Array{
private:

        int size;
        Element** A;

		void powieksz(int i);
public:
       Array();
	   Array(const Array& B);

       ~Array();
	   int getSize();

	   bool operator= (const Array & B);
       Array operator+ (const Array & B);
       
       void dodaj(Element*);
	   void dodaj(const int i, Element* e);
	   void usun(const int i);
       void wypisz();  
       Element*& operator[] (const int i);

	   //-1 gdy nie ma e w tablicy lub pozycja e
	   int find(Element* e);
       
       friend ostream& operator<< (ostream& str, const Array<Element> & B);
        
};
 

I definicja przeciążenia:

template<class Element>
ostream& operator<<(ostream& str, const Array<Element>& B) 
{
	str << "Tablica zawiera "<<B.size<<" elementow:"<<endl;
	 for(int i = 0; i < B.size; i++)
     {	
		 if (B.A[i] == NULL)
             cout << "NULL ";
		 else
			 cout << *B.A[i] << " ";
		 cout << endl;
           
     }
	return str;
} 
0

Zakładam że to z powodu że podzieliłeś to na pliki .h i .cpp
Klasy wzorcowe muszą być w całości umieszczone w pliku .h
Owszem powyższe jest w takim samym stopniu prawda jak to co w pierwszych klasach podstawówki mówią dzieciom: - "zawsze od większej liczby odejmuje się mniejszą" ale dla początkującego to naprawdę dobra rada :D
Z drugiej strony zawsze radzę unikać friend'ów o ile to możliwe, np:

template<class Element> class Array
  {
   ...
   public:
   ...
   ostream &prn(ostream &str)const;
  };
template<class Element> ostream &operator<<(ostream &str, const Array<Element> &B) { return B.prn(str); }

template<class Element> ostream &Array<Element>::prn(ostream &str)const
  {
   str<<"Tablica zawiera "<<size<<" elementow:"<<endl;
   // na temat powyższego wiersza:
   // co byś powiedział gdyby kod:
   // int i=5; cout<<i;
   // wypluł na ekran następujący dwuwierszowy tekst:
   // Zmienna ma rozmiar 4 bajty
   // 5
   // jaka byłaby użyteczność operacji cout<<i; w takim przypadku?
   for(int i=0;i<size;++i)
   ...
   return str;
  }
0
bool operator= (const Array & B);

tu chyba miało być ==?

void dodaj(const int i, Element* e);

const jest zbędne.

Poza tym metody takie jak find czy wypisz powinny być const.

0

@_13th_Dragon
Wiem, że bezpieczniej byłoby trzymać wszystko w jednym pliku, ale to już odgórne zarządzenie ćwiczeniowca, żeby dzielić.

C/C++ niestety nie jest moją najmocniejszą stroną.
Przerzuciłem to do jednego pliku, ale nadal ten operator wypisania powoduje masę błędów. Wrzucam zawartość obu plików, żeby można było to ogarnąć jako całość.

Array.h

#ifndef ARRAY_H
#define ARRAY_H

using namespace std;


template<class Element>
class Array{
private:

        int size;
        Element** A;

		void powieksz(int i);
public:
       Array();
	   Array(const Array& B);

       ~Array();
	   int getSize();

	   bool operator= (const Array & B);
       Array operator+ (const Array & B);
       
       void dodaj(Element*);
	   void dodaj(int i, Element* e);
	   void usun(const int i);
       void wypisz();  
       Element*& operator[] (const int i);

	   //-1 gdy nie ma e w tablicy lub pozycja e
	   int find(const Element* e);

     template<Element>
      friend ostream& operator<< (ostream& str, const Array<Element>& B);
       

};

template<typename Element>
struct Komp {
	Komp() {}
	int operator()(Array<Element>& a1, Array<Element>& a2) {
		return a1.getSize() < a2.getSize();
	}
};

template<class E>
ostream& operator<<(ostream& str, const Array<E>& B) 
{
	str << "Tablica zawiera "<<B.size<<" elementow:"<<endl;
	 for(int i = 0; i < B.size; i++)
     {	
		 if (B.A[i] == NULL)
             cout << "NULL ";
		 else
			 cout << *B.A[i] << " ";
		 cout << endl;
           
     }
	return str;
}
#endif
 

I Array.cpp

#include "array.h"
#include <iostream>
using namespace std;

const int PRZYROST = 4;

template<class Element>
Array<Element>::Array()
{
	cout<<"Konstruktor pustej tablicy"<<endl;
              size = PRZYROST;
              A = new Element*[size];
              for(int i = 0; i < size; i++)
				  A[i] = NULL;
    wypisz();
}

template<class Element>
Array<Element>::Array(const Array& B) {
	cout<<"Konstruktor kopiujacy tablicy"<<endl;
              size = B.size;
              A = new Element*[size];
              for(int i = 0; i < size; i++)
				  A[i] = B.A[i];
    wypisz();
}

template<class Element>
int Array<Element>::getSize() {
	return size;
}

template<class Element>                        
void Array<Element>::wypisz()
{
     cout << "Tablica " << size << " elementowa: " <<endl;
     for(int i = 0; i < size; i++)
     {	
		 if (A[i] == NULL)
             cout << "NULL ";
		 else
			 cout << *A[i] << " ";
		 cout << endl;
           
     }
}

template<class Element> 
void Array<Element>::powieksz(int i)
{
	i++;
	int oldSize = size;
	int newSize;
	int reszta = (i % PRZYROST);
	if (reszta == 0) newSize = i;
	else if (reszta == 1) newSize = i + 3;
	else if (reszta == 2) newSize = i + 2;
	else if (reszta == 3) newSize = i + 1;
	Element **newA = new Element*[newSize];
	for (int j = 0; j < oldSize; j++)
		newA[j] = A[j];
	for (int j = oldSize; j < newSize; j++) {
		newA[j] = NULL;
	}
	delete [] A;
	A = newA;
	size = newSize;
}

template<class Element> 
Element*& Array<Element>::operator[] (const int i)
{
	if (i < 0) {
		throw "Ujemny wskaznik do tablicy - blad";
	}
	if (i < size) {
        return A[i];
	}
	else {
		powieksz(i);//powieksza i kopiuje
		return A[i];
	}
}

template<class Element>
Array<Element>::~Array()
{               
      delete[] A;
}


template<class Element>
void Array<Element>::dodaj(Element* element)
 {

	 int i = 0;
	 while ((i < size) && (A[i] != NULL))
		 i++;
	 if (i == size) {
		 //nie ma wolnego miejsca
		 powieksz(size);
		 A[size] = element;
	 } else {
		 //jest wolne miejsce
		 A[i] = element;
	 }
}

template<class Element>
void Array<Element>:: dodaj(int i, Element* element)
{
	

	 if (i >= size) {
		 //nie ma wolnego miejsca
		 powieksz(i);
		 A[i] = element;
	 } else {
		 //jest wolne miejsce
		 A[i] = element;
	 }
}

template<class Element>
void Array<Element>::usun(const int i)
{
	if (i < 0) {
		throw "Ujemny wskaznik do tablicy - blad";
	}
	if (i < size)
		A[i] = NULL;
}

template<class Element>
bool Array<Element>::operator= (const Array<Element> & B)
{
	delete [] A;
	size = B.size;
	A = new Element* [size];
	for (int i = 0; i < size; i++)
		A[i] = B.A[i];
	return true;
}

template<class Element>
Array<Element> Array<Element>::operator+ (const Array<Element> & B)
{
	Array<Element> * C = new Array<Element>;
	
	for (int i = 0; i < this->size; i++)
		(*C)[i] = A[i];

	for (int i = 0; i < B.size; i++)
		(*C)[this->size + i] = B.A[i];

	return *C;
}


template<class Element>
int Array<Element>::find(const Element* e) {
	for (int i = 0; i < size; i++) {
		if (*A[i] == *e) {
			return i;
		}
	}
	return -1;
}

 
0
_13th_Dragon napisał(a):

Klasy wzorcowe muszą być w całości umieszczone w pliku .h
Którego słowa nie rozumiesz?

0

Sorry, już działa. Dzięki

0

Operator przypisania dalej jest źle. On musi zwracać Array<Element> &. Dodatkowo nie sprawdzasz przypisania do samego siebie:

Array<int> a;
// ...
a = a;

// To zrobi tak:

delete [] A; // usunie tablicę z obiektu a
A = new Element* [size]; // Stworzy ją na nowo w obiekcie a
for (int i = 0; i < size; i++)
  A[i] = B.A[i]; // Ponieważ B == a będzie czytać z usuniętego obszaru pamięci.
0
_13th_Dragon napisał(a):
_13th_Dragon napisał(a):

Klasy wzorcowe muszą być w całości umieszczone w pliku .h
Którego słowa nie rozumiesz?

A wcale ze nie ;) Ostatnio w temacie @Zjarek i @Tezcatlipoca powiedzieli jak mozna podzielic dobrze klase szablonowa na pliki.

Przyklad

#ifndef LIST_H
#define LIST_H

#include <iostream>

template <class Node>
class List
{
private:
    Node *first;
    Node *last;
public:
    List();
    List(int size);
    List(const List<Node> &ls);
    ~List();
    Node at(int i);
    const Node& operator[](int i)const;
    Node &operator[](int i);
    Node &operator=(Node n);
    List<Node> &operator=(List<Node> &ls);
    int size()const;
    Node* begin()const;
    Node* end()const;
    template<Node>friend std::ostream &operator<<(std::ostream &os, List<Node>&ls);
    void resize(int size);
    Node* pop();
    void push(Node n);
};

#include "list.cpp" // <--- wazne

#endif // LIST_H

Wszystko dziala i nie trzeba bylo w jednym pliku pisac.

0

A wcale ze nie

ale to jest oszustwo, bo wiadomo że z punktu widzenia kompilatora nie ma znaczenia czy w .cpp jest include do .h, czy odwrotnie: wszystko jest łączone w jedną jednostkę kompilacji.

ale co powiesz na to:

main.cpp

#include "ala.h"
#include <string>

using namespace std;

int main()
{
   Kot<int> kotek1;
   kotek1.Miaucz(3);

   Kot<string> kotek2;
   kotek2.Miaucz("trzy");
}

ala.h

#ifndef ALA_H
#define ALA_H

template <typename T>
struct Kot
{
    void Miaucz(T p);
};

#endif

ala.cpp

#include "ala.h"

#include <iostream>
#include <string>
using namespace std;

template <typename T>
void Kot<T>::Miaucz(T p)
{
   cout << "miau "<< p << endl;
}

// tu musimy „przewidzieć” wszystkie typy parametryczne, inne nie będą dostępne w innych plikach .cpp
template struct Kot<int>;
template struct Kot<string>;

Taki kod zadziała. Można rozdzielić template'a na .h i .cpp, ale trzeba wtedy ręcznie pomóc kompilatorowi wygenerować kod klasy szablonowej dla wszystkich potrzebnych parametrów (lub kombinacji parametrów). To oczywiście spore ograniczenie, ale może się przydać.

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