Skocz do zawartości
Puchacz1

[C++] Moje pytania dotyczące języka

Rekomendowane odpowiedzi

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 przez Puchacz1

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

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 przez Vennor

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

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> &current, 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 przez Vennor

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

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 przez Puchacz1

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

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 przez Vennor

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

Gość <account_deleted>

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 ;)

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

Gość <account_deleted>

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.

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

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 przez Puchacz1

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

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 przez Thomas

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

Dołącz do dyskusji

Możesz dodać zawartość już teraz a zarejestrować się później. Jeśli posiadasz już konto, zaloguj się aby dodać zawartość za jego pomocą.

Gość
Dodaj odpowiedź do tematu...

×   Wklejono zawartość z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Odnośnik został automatycznie osadzony.   Przywróć wyświetlanie jako odnośnik

×   Przywrócono poprzednią zawartość.   Wyczyść edytor

×   Nie możesz bezpośrednio wkleić grafiki. Dodaj lub załącz grafiki z adresu URL.

Ładowanie


×
×
  • Dodaj nową pozycję...