Wykłady odbywają się w środy o 18:30 na platformie MS Teams.
Kod do zespołu został przekazany drogą mailową
Kurs na platformie UPEL: https://upel.agh.edu.pl/course/view.php?id=8307
Podczas egzaminu:
Proszę przynieść własne kartki formatu A4, np. 3 na odpowiedzi oraz 3 na brudnopis i 2 długopisy.
Bez STL
Użyj STL
set<T>
elementów, które nie mogą być w nim umieszczone z powodu braku możliwości ich porównywaniaset<T>
, aby sprawdzić czy zawiera element - O(n) zamiast O(log n)trg.a=towary[i].a; trg.b=towary[i].b; trg.c=towary[i].c; trg.d=towary[i].d;
Wystarczy trg=towary[i]
Jeżeli tematem było napisanie szablonów klas typu Set<T>
lub Wielozbiór<T>
z zastrzeżeniami dotyczącymi dostępnych operatorów dla klasy będącej parameterem szablonu, to zapewne nie można było użyć std::set<T>
lub std::map<T>
.
Szablony unordered_set
lub unordered_map
działają na klasach “haszowalnych”, tzn. takich dla których da się obliczyć wartość indeksu w tablicy na podstawie zawartości.
Proszę spróbować skompilować:
class A{ public: A(int _v):v(_v){} int v; bool operator==(const A&a)const{ return v==a.v; } }; int main(){ std::unordered_set<A> set; set.insert(A(1)); std::cout<<(set.find(A(1))!=set.end())<<std::endl; }
Dla nietypowej własnej klasy należy dostarczyć własny obiekt funkcyjny do obliczania “hasza” (czyli położenia w tablicy)…
class A{ public: A(int _v):v(_v){} int v; bool operator==(const A&a)const{ return v==a.v; } }; class MyHashForA{ public: size_t operator()(const A&a)const{ return std::hash<int>{}(a.v); } }; int main(){ std::unordered_set<A,MyHashForA> set; set.insert(A(1)); std::cout<<(set.find(A(1))!=set.end())<<std::endl; }
Funkcja nie może zwracać wskaźnika po dereferencji do obiektu, dla którego pamięć przydzielono na stercie. Jak zwolnić tę pamięć? Memory leak…
class A{ public: int v; }; A bardzo_zle_napisana_funkcja(){ A*ptr = new A(); ptr->v=10; printf("ptr=%p\n",ptr); return *ptr; } int main(){ A a1 = bardzo_zle_napisana_funkcja(); printf("&a1=%p\n",&a1); A a2; a2=bardzo_zle_napisana_funkcja(); printf("&a2=%p\n",&a2); // jak zwolnić ptr? } // Wynik ptr=0x80004add0 &a1=0xffffcc3c ptr=0x80004ae40 &a2=0xffffcc38
Proszę zatwierdzać test. Jest na to 10 minut po upływie czasu
sizeof(text) ma wartość 8 (na platformie 64-bitowej). Długość tekstu obliczamy za pomocą strlen()
TextListElement::TextListElement(const char *txt) { value = new char[sizeof(txt)]; strcpy(value, txt); next = 0; prev = 0; }
c:/
, c:/user
ale nie zjedziemy do c:/user/kasia
bo nasza ściezka będzie miała postać user/kasia
Directory&Directory::operator=(const Directory&other){ if(&other!=this){ free(); copy(other); } return *this; }
void Directory::free(){ for (auto e:entries)delete e; entries.clear(); }
void Directory::copy(const Directory&other){ this->name = other.name; // NIE: this->parent = other.parent; this->parent = nullptr; // Obiekt nardrzędny ustawi wskaźnik w instrukcji (2) //(1) kopiowanie for(auto e:other.entries){ // entries.push_back( kopia e) } // (2) for(auto e:entries){ e->parent=this; } }
Poniższe rozwiązanie jest poprawne, ale dyskusyjne… Nieporzebnie stosowane jest rzutowanie w dół hierarchii. Klasa u szczytu hierarchii musiałaby znać wszystkie klasy potomne.
Dirent *Dirent::find(const char *name) { if (is_dir()) { for (auto element : dynamic_cast<Directory *>(this)->entries) { if (element->name == name) { return element; } else if (element->is_dir()) { if (element->find(name)) { return element->find(name); } } } } }
Zamiast tego (jeżeli decydujemy się na wprowadzenie do interfejsu klasy bazowej):
// zadeklarowane jako virtual Dirent *Dirent::find(const char *_name){ if (name==_name ) return this; return nullptr; } Dirent *Directory::find(const char *_name) { if (name==_name ) return this; // lub: //auto e = Dirent::find(_name); //if(e) return e; for (auto element : entries) { auto e = e->find(_name); if(e)return e; } return nullptr;//0 }
Po co downcasting w przypadku funkcji wirtualnych?
for (auto& e:entries) { if(e->is_dir()) dynamic_cast<Directory*>(e)->list(os, indent); if(e->is_file()) dynamic_cast<File*>(e)->list(os, indent); }
capacity > size()+strlen(_txt)
lub capacity > size()+other.size()
strlen()
, nie działają dla zerowych wskaźników. Dyskusja strcat()
i strcpy()
. Te funkcje przynajmniej nie zapomną o zerze na końcu!bool String::operator==(const char*_txt)const{ if(_txt == this->txt) return true; // BŁĄD return false; }
operator=
pamiętamy o if(&other!=this)
- za to są odliczane punkty na egzaminieString operator+(Arg arg){ String ret(*this); ret += arg; return ret; }
set_union
lub insert(other.begin(),other.end())
):auto domain = get_domain(); auto range = get_range(); set<int> all(domain); for(auto e:range)all.insert(e);
for(int i=0;i<pairs.size();i++){ if(has_pair(pairs[i].x,pairs[i].y)... }
bool relation::is_antisymmetric()const{ for(auto&e:pairs){ if(e.x!=e.y && has_pair(e.y,e.x))return false; } return true; }
class relation{ public: class pair{ public: int x; int y; bool operator<(const pair&other)const; pair(int _x, int _y):x(_x),y(_y){} }; public: #if defined USE_VECTOR vector<pair> pairs; #endif #if defined USE_SET set<pair> pairs; #endif ... itd
dwie zmiany:
bool relation::has_pair(int x,int y)const{ #if defined USE_VECTOR for(int i=0;i<pairs.size();i++){ if(pairs[i].x==x && pairs[i].y==y)return true; } return false; #endif #if defined USE_SET auto it = pairs.find(pair(x,y)); return it!=pairs.end(); #endif void relation::add(int x,int y){ #if defined USE_VECTOR if(!has_pair(x,y))pairs.emplace_back(x,y); #endif #if defined USE_SET pairs.insert(pair(x,y)); #endif }
Przy zmianie struktur danych żadna inna funkcja nie wymagała modyfikacji
Po to napisaliśmy metodę Student::write, aby jej użyć, a nie pisać jak poniżej:
void StudentList::write(ostream&os)const{ for(int i=0;i<count;i++){ os<<tablica[i].indeks<<'\t'<<tablica[i].imie<<'\t'<<tablica[i].nazwisko<<'\t'; os<<tablica[i].skreslony<<'\t'<<tablica[i].grupa<<'\t'; } }
if(ok1){ if(ok2){ if(ok3){ // Tu dobry ciąg instrukcji... }else{ } }else{ } }else { }
… nikt nie sprawdzi tych warunków else
U prawie wszystkich linspace()
wygeneruje wyjątek dla n=1
Logika zabezpieczeń
void Fibo1::init(int _n) { if(_n<=0)tab=0; n = _n; tab = new int[n]; } void Fibo1::destroy() { delete [] tab; } ArithmeticSequence(int _n, double _r, double _first) { if(_n<=0)return; // USTAW n=0, tab=0 n=_n; r=_r; first=_first; tab = new double[n]; } void Fibo1::init(int n) { if(n <= 0) { tab = 0; } tab = new int[n]; } void Fibo1::init(int _n) { n = _n; int* tab = new int[_n]; if(n <= 0){ tab = NULL; } } Fibo::~Fibo(){ if(n>0) delete []tab; // TO JEST POPRAWNE, ale proponuję raczej idiom if(tab) delete []tab; }
Class::member służy do czegoś innego…
for (int _i = 0; _i < ArithmeticSequence::n; _i++) { cout << ArithmeticSequence::arr[_i] << endl; } void Fibo1::fill() { tab[0]=1,tab[1]=1; // a jak tab==0 lub n=1? for(int i=2;i<n;i++) tab[i]=tab[i-1]+tab[i-2]; }
Bez średnika!
#define N 10;
To nie jest stała, to jest VLA
Variable Length Arrays nie należą do standardu C++. Zamiast tego należy używać vector<int>
void fibo1(){ int n; int tab[n];