Puchacz1 Opublikowano 1 Czerwca 2011 Zgłoś Opublikowano 1 Czerwca 2011 (edytowane) Pozwólcie, że będę tu pytał o nurtujące mnie pytania związane z językiem programowania C++. Pytanie nr. 1: Dlaczego tworząc dynamiczną tablicę w funkcji zwracającej tą tablicę (przez wskaźnik) nie muszę zwalniać zaalokowanej przez nią pamięć przy kończeniu pracy programu. [przykładowy kod] int *tablica(n){ int *asd = new int [n]; return asd;}int main(){ int *zmienna = tablica(10); return 0;}Pytanie nr. 2: Dlaczego nie mogę określić rozmiaru tablicy zadeklarowanej w funkcji zwracającej wskaźnik na pierwszy element tablicy. Czy nie jest to przypadkiem dlatego, że zwracając wskaźnik na pierwszy element gubię informację gdzie kończy się tablica? (mogę wędrować po "nieswojej" części pamięci?). Czy rozwiązaniem nie byłoby zwracaniem tablicy wskaźników na pierwszy i ostatni element? Oczywiście mogę to rozwiązać tworząc tablicę zawsze o 1 element większą i zapisując w niej jakąś umowną wartość a następnie napisać funkcję sprawdzająca rozmiar jednak chodzi o sam fakt "dlaczego tak się dzieje". int *tablica(n){ int *asd = new int [n]; return asd;}int main(){ int *zmienna = tablica(10); //tu chcę zbadać rozmiar tablicy (np. sizeof) return 0;}Pytanie nr.3 Czy mając tablicę wskaźników mogę wpisywać do takiej tablicy wskaźniki na różne typy danych? (string, int, double) i to chwilowo wszystko :) Edytowane 1 Czerwca 2011 przez Puchacz1 Cytuj Udostępnij tę odpowiedź Odnośnik do odpowiedzi Udostępnij na innych stronach Więcej opcji udostępniania...
Vennor Opublikowano 2 Czerwca 2011 Zgłoś Opublikowano 2 Czerwca 2011 (edytowane) 1. Dokładniej: zwalnianie zadeklarowanej pamięci nie jest obowiązkowe, odbywa się automatycznie podczas kończenia pracy programu. Nikt ci jednak nie powie, że to mądre. Szczególnie w przypadku dużych projektów, gdzie najmniejszy nawet wyciek pamięci może spowodować katastrofę. W twoim przykładzie powinieneś użyć delete []. Jeżeli nie jesteś pewien, kiedy powinno się to stać, albo nie chcesz się tym przejmować, zastosuj auto_ptr. Ponadto twój kod można zapisać efektywniej: int *tablica(const int &n) { return new int[n];}2. Powiada się, że operator sizeof to tzw. "compile statement", przeto korzysta z danych dostępnych wyłącznie podczas kompilacji. Nie wiem, ile w tym prawdy, ponieważ nic nie stoi na przeszkodzie, by stworzyć tablicę o rozmiarze podanym w czasie pracy programu. I sizeof zadziała wobec niej poprawnie. A rozmiar tablicy utworzonej przez operator new musi być gdzieś dostępny, ponieważ w przeciwnym wypadku delete [] nie mogłoby działać. To interesująca kwestia, której nie udało mi się jeszcze rozwiązać. Tymczasem odsyłam na StackOverflow. To istna kopalnia wiedzy, więc odpowiedź musi gdzieś tam być. Może znajdziesz ją prędzej niż ja. Rozsądną alternatywą jest użycie kontenera STL - jako pierwszy poznaje się zazwyczaj vector - i obliczać rozmiar na przykład tak: foo.size() * sizeof(foo[0]). Jeżeli uznasz to za niewygodne - możesz napisać własną klasę, która dziedziczy po klasie vector, i dodać nową funkcję, która zwróci size() * operator[](0). Jeśli zaś vector jest dla ciebie za duży i ociężały, nic nie stoi na przeszkodzie, abyś napisał własny kontener. W C++ to dość łatwe zadanie, mimo że wykonanie go na szóstkę wymaga już sporej wiedzy. Poniżej bardzo prosty, pisany naprędce i zapewne zawierający błędy przykład. template <typename T>class myPtr { public: myPtr<T>(const int); ~myPtr<T>(); //myPtr<T>(const myPtr &); //myPtr<T> &operator=(const myPtr); //T &operator[](const int); int size() const; void resize(const int); private: int typeSize; int length; T *ptr;};template <typename T>myPtr<T>::myPtr(const int desiredLength) : ptr(new T[desiredLength]), typeSize(sizeof(T)), length(desiredLength) {}template <typename T>myPtr<T>::~myPtr() { delete [] ptr;}template <typename T>inline int myPtr<T>::size() const { return (length * typeSize);}template <typename T>inline void myPtr<T>::resize(const int desiredLength) { T *oldPtr = ptr; ptr = new T[desiredLength]; copy(oldPtr, oldPtr + length, ptr); length = desiredLength; delete [] oldPtr;}Oczywiście musisz zadbać przede wszystkim o przemyślany konstruktor kopiujący i operator przypisania, których poprawne działanie jest szczególnie istotne podczas zarządzania pamięcią. Zalecam zapoznać się z copy-and-swap. Przyda się operator[], a jeśli uznasz to za konieczne - również operator wyłuskania. Zaimplementowanie obsługi błędów przynajmniej w postaci asercji może okazać się pomocne. Możliwości jest więcej: na przykład prosta struktura o dwóch polach: rozmiar i wskaźnik. 3. O ile to tablica wskaźników typu void - owszem. Jeżeli jednak kontrola typów stoi ci na przeszkodzie, to wybrałeś nieodpowiedni język, jest ona bowiem jedną z najistotniejszych cech C++. Edytowane 2 Czerwca 2011 przez Vennor Cytuj Udostępnij tę odpowiedź Odnośnik do odpowiedzi Udostępnij na innych stronach Więcej opcji udostępniania...
Vennor Opublikowano 8 Czerwca 2011 Zgłoś Opublikowano 8 Czerwca 2011 (edytowane) Co postanowiłeś w związku z pytaniem numer dwa, Puchaczu? Mnie zainteresował pomysł stworzenia osobnego kontenera i rozwinąłem przykład z poprzedniego posta. Wyszło mi to, co znajdziesz poniżej. Wprawdzie nie jest to pokaz mistrzowskiego programowania i jestem przekonany, że po właściwym użyciu kodu znajdą się błędy; być może również coś przeoczyłem i okaże on kompletnie bezużyteczny, tym niemniej jeśli uznasz, że ci cię przyda, możesz go wykorzystać. Dopisek: odkryłem, że użycie operatora przypisania powoduje problem z niszczeniem obiektu. Powodem jest najpewniej współdzielenie wskaźników przez różne obiekty. Odebranie kontenerowi odpowiedzialności za wskaźniki jest najprostszym rozwiązaniem. Można też uniemożliwić użycie konstruktora kopiującego i operatora przypisania przez uczynienie ich prywatnymi. Być może pomocne okazałoby się implicit sharing. Werdykt: kod w obecnej postaci jest zły. #include <algorithm>#include <cstddef>const std::size_t MIN_SIZE = 5;template <typename T>class myPtr { public: myPtr<T>(const unsigned = 0); myPtr<T>(const myPtr<T> &); ~myPtr<T>(); // Przejmuje odpowiedzialność za wskaźniki (!) myPtr<T> &operator=(myPtr<T>); T &operator[](const unsigned); // Referencja do wartości (rvalue) std::size_t dataSize() const; // Łączna wielkość elementów std::size_t size() const; // Liczba elementów std::size_t capacity() const; // Aktualna pojemność void resize(const unsigned); void remove(const unsigned); void replace(const unsigned, T *); void push_back(T *); private: void swap(myPtr<T> &, myPtr<T> &); void more(); void less(); std::size_t typeSize; std::size_t length; std::size_t next; T **ptr;};//-----------------------------------------------------------------C-TION/D-TIONtemplate <typename T>myPtr<T>::myPtr(const unsigned newLength /* = 0 */) : ptr(newLength > 0 ? new T *[newLength] : 0), length(newLength > 0 ? newLength : 0), next(0), typeSize(sizeof(T)){ for (int i = 0; i < length; ++i) *(ptr + i) = 0;}template <typename T>myPtr<T>::myPtr(const myPtr &that) : ptr(that.length > 0 ? new T *[that.length] : 0), length(that.length > 0 ? that.length : 0), next(that.next), typeSize(sizeof(T)){ std::copy(that.ptr, that.ptr + length, ptr);}template <typename T>inline myPtr<T>::~myPtr() { for (int i = 0; i < next; ++i) delete ptr[i]; delete [] ptr;}//---------------------------------------------------------------------------OPStemplate <typename T>myPtr<T> &myPtr<T>::operator=(myPtr<T> that) { swap(*this, that); // Dla bezpieczeństwa można użyć this->swap(); return *this;}template <typename T>inline T &myPtr<T>::operator[](const unsigned index) { return **(ptr + index);}//-----------------------------------------------------------------------METHODStemplate <typename T>inline std::size_t myPtr<T>::dataSize() const { return (next * typeSize);}template <typename T>inline std::size_t myPtr<T>::size() const { return next;}template <typename T>inline std::size_t myPtr<T>::capacity() const { return length;}template <typename T>void myPtr<T>::resize(const unsigned newLength) { T **oldPtr = ptr; ptr = new T *[newLength]; std::copy(oldPtr, oldPtr + length, ptr); length = newLength; delete [] oldPtr;}template <typename T>void myPtr<T>::remove(const unsigned index) { delete (ptr + index); for (int i = index; i < size() - 1; ++i) *(ptr + i) = *(ptr + i + 1); --next; less();}template <typename T>void myPtr<T>::replace(const unsigned index, T *newPtr) { delete *(ptr + index); *(ptr + index) = newPtr;}template <typename T>void myPtr<T>::push_back(T *newPtr) { more(); *(ptr + next++) = newPtr;}template <typename T>void myPtr<T>::swap(myPtr<T> ¤t, myPtr<T> &that) { std::swap(current.ptr, that.ptr); std::swap(current.next, that.next); std::swap(current.length, that.length);}template <typename T>inline void myPtr<T>::more() { if (next >= length) resize(length < MIN_SIZE ? MIN_SIZE : length * 2);}template <typename T>inline void myPtr<T>::less() { if ((next <= length * 0.5) && (length > MIN_SIZE)) resize(length * 0.5 < MIN_SIZE ? MIN_SIZE : length * 0.5);} Edytowane 8 Czerwca 2011 przez Vennor Cytuj Udostępnij tę odpowiedź Odnośnik do odpowiedzi Udostępnij na innych stronach Więcej opcji udostępniania...
Puchacz1 Opublikowano 22 Czerwca 2011 Zgłoś Opublikowano 22 Czerwca 2011 (edytowane) nie jestem aż tak biegły żeby ogarnąć szablony (jeszcze) niemniej jednak dzięki i na pewno się przyda!. Dopiero co wkroczyłem w świat programowania obiektowego a problem z pytania nr. 2 rozwiązałem najprościej jak się tylko dało. Tworzę o 1 większą tablicę i wpisuję w nią wartość charakterystyczną którą mam pewność, że nie wystąpi. Napisałem funkcję rozmiar przelatującą po tablicy aż do momentu znalezienia tej liczby i zwracającej rozmiar. Rozwiązanie ma wiele wad ale nic bardziej zaawansowanego nie było mi po prostu potrzebne. Kolejne pytanie: 4. Jak stworzyć własny operator i nie chodzi tutaj o przeładowanie operatorów np. += tylko o tworzenie własnego operatora działającego na danej klasie (np. operator ^T) w tym przypadku dokonujący transpozycji macierzy. Nie mogłem tego nigdzie znaleźć a i książki nie wiele mi powiedziały. 5. Jak pisać funkcję tak aby środowisko zamiast wyświetlania podpowiedzi typu (int x, bool y) wyświetlało (podpowiedź 1, podpowiedź2). Wydaje mi się, że trzeba jakoś inaczej zapisać prototyp funkcji ale nie mogłem tego znaleźć. Edytowane 22 Czerwca 2011 przez Puchacz1 Cytuj Udostępnij tę odpowiedź Odnośnik do odpowiedzi Udostępnij na innych stronach Więcej opcji udostępniania...
Vennor Opublikowano 23 Czerwca 2011 Zgłoś Opublikowano 23 Czerwca 2011 (edytowane) 4. W C++ nie można tworzyć nowych operatorów; wyłącznie przeciążać istniejące tak, by wykorzystywały tę samą liczbę argumentów. Półśrodek: const bool T = 0;class XORov { public: XORov(int xIn) : x(xIn) {}; XORov &operator^(const bool) { // ... } private: int x;};int main() { XORov a(1); a ^ T;} 5. Nazwy argumentów nie stanowią wystarczających podpowiedzi? W każdym razie: to najpewniej zależy od środowiska i jego funkcji. By zrobić to z poziomu języka, spróbuj wykorzystać typedef - co jednak może wywołać skutek odwrotny do oczekiwanego i wprowadzić nieporządek w kodzie. Przykład: typedef int IleZjeszCiastek;typedef bool NapijeszSieMleka;void foo(IleZjeszCiastek count, NapijeszSieMleka condition) { // ...} Edytowane 23 Czerwca 2011 przez Vennor Cytuj Udostępnij tę odpowiedź Odnośnik do odpowiedzi Udostępnij na innych stronach Więcej opcji udostępniania...
Gość <account_deleted> Opublikowano 24 Czerwca 2011 Zgłoś Opublikowano 24 Czerwca 2011 Overloading operatorów służy głównie do tego, żeby żródło wyglądało "ładnie". Najczęściej prowadzi to do zgubienia sensu, czy może raczej poczucia co dzieje się w kodzie wynikowym (ukrywa fakt wywołania funkcji oraz realokacje pamięci, sortowanie itp.). Ale to już inna bajka. Co do operatorów np. MyString1 += Mystring2; to nic innego jak MyString1.Add(Mystring2); albo AddString(&Mystring1, &Mystring2); więc w tym sensie można tworzyć dowolne "operatory" rozumiane jako metody lub funkcje, bo operator to po prostu wywołanie funkcji. 3. O ile to tablica wskaźników typu void - owszem. Jeżeli jednak kontrola typów stoi ci na przeszkodzie, to wybrałeś nieodpowiedni język, jest ona bowiem jedną z najistotniejszych cech C++.Z tym, że Variant to nie Void ;) Cytuj Udostępnij tę odpowiedź Odnośnik do odpowiedzi Udostępnij na innych stronach Więcej opcji udostępniania...
Vennor Opublikowano 24 Czerwca 2011 Zgłoś Opublikowano 24 Czerwca 2011 Z tym, że Variant to nie Void ;) Nie miałem jeszcze okazji poznać boosta, co nie zmienia faktu, że wskaźnik na void przyjmuje adresy obiektów wszystkich typów. Cytuj Udostępnij tę odpowiedź Odnośnik do odpowiedzi Udostępnij na innych stronach Więcej opcji udostępniania...
Gość <account_deleted> Opublikowano 25 Czerwca 2011 Zgłoś Opublikowano 25 Czerwca 2011 Typ Void pointera to po prostu adres w pamięci - ma dokładnie takie same znaczenie jak w ASM i stosuje się go również w ten sam sposób - trzeba wiedzieć co znajduje się pod konkretnym adresem. Nie tyle "przyjmuje adresy wszystkich typów" co pozwala na typecasting, który jednak niewiele pomaga, ponieważ można np. odczytać float z fragmentu int i wyjdzie bzdura. Poza tym kompilatory i systemy zarządzania pamięcią wyrównują struktury danych (zazwyczaj w zależności od architektury do 4 lub 8 bajtów) co oznacza że mogą pojawiać "dziury" pomiędzy strukturami a nawet adresami poszczególnych zmiennych - to dodatkowo utrudnia stosowanie Void. Niemniej jednak nie istnieje nic szybszego i bardziej uniwersalnego niż "czysty" pointer. Cytuj Udostępnij tę odpowiedź Odnośnik do odpowiedzi Udostępnij na innych stronach Więcej opcji udostępniania...
Puchacz1 Opublikowano 5 Lipca 2011 Zgłoś Opublikowano 5 Lipca 2011 (edytowane) pytanie nr. 6 To pytanie bardziej w kwestii algorytmiki niż samego C++. Potrzebuję zrobić menu w którym będę mógł cofać się wstecz. Chyba zrobiłbym to nieskończoną pętlą while() i ewentualnie warunkami.Ogólnie wyglądałoby to tak: while(true){ switch zmienna { case:0 while(true) { if przerwij == 1 break; else while() <--kolejny poziom menu } case:1 while(true) { if przerwij == 1 break; else while() <--kolejny poziom menu } }}oczywiście odpowiednio bym sobie tym sterował tak abym uzyskał przejście z menu do podmenu jednak zastanawiam się czy nie ma innego sposobu. Poza goto bo to zapewne jest złą praktyką :) Edytowane 5 Lipca 2011 przez Puchacz1 Cytuj Udostępnij tę odpowiedź Odnośnik do odpowiedzi Udostępnij na innych stronach Więcej opcji udostępniania...
Thomas Opublikowano 6 Lipca 2011 Zgłoś Opublikowano 6 Lipca 2011 (edytowane) Nie wiem jak wielkie ma być to menu - ale jeśli graficzne to pobawiłbym się w klasy - klasa 'główne menu' - ze zmienną - 'lista menu' (array) w której byś przechowywał wpisy - pojedyńcze menu (obiekt klasy 'pojedyńcze menu'). I wtedy możesz wpisać w każde pojedyńcze menu, z jakiego obiektu przychodzi i co wyświetlać w momencie gdy naciśniesz w nim wstecz (w jaki sposób przeładować główne menu - jakimi obiektami to wypełnić) i co zrobić jak na nie klikniesz (czy wykonać jakąś akcje czy zmienić menu na podmenu). Wydaje mi się to sporo roboty na jedno proste menu z drugiej strony jak raz to napiszesz to będziesz to mógł wykorzystać w dowolnym języku. PS Raczej nieefektywną metodą, ale szybką do zaimplementowania byłoby oparcie go na readln, stany i jakies ify - np.: a)Pokaż kalkulator b)Pokaż notatnik q)Quit Wpisz rozkaz:a (zmienna state==0) i po przeczytaniu co wpisałeś wypisujesz nowe menu i Clear state a) Dodaj b) Odejmij z) Wróć Wpisz rozkaz: (zmienna state == 1 czyli kalkulator i sprawdzacz dużym ifem wczytaną literę if (state==1 and liter=='z') then state=0; przerysujMenu(); itd itp) Edytowane 6 Lipca 2011 przez Thomas Cytuj Udostępnij tę odpowiedź Odnośnik do odpowiedzi Udostępnij na innych stronach Więcej opcji udostępniania...