Program z klasami - pomoc ze zrozumieniem kodu

0

Potrzebuję pomocy ze zrozumieniem tego kodu.Wszelkie komentarze po bokach dodawałem sam i mogą być błędne. Jest to praca domowa polegająca głównie na zrozumieniu działania kodu, a ciężko mi to wychodzi. Pytania pod kodem :)
kod:

#ifndef __RCSTRING_H__
#define __RCSTRING_H__
#include <string.h>
#include <stdio.h>
#include <malloc.h>
#include <iostream>
using namespace std;

class rcstring 
{
    struct rctext;
    rctext* data;

public:
    class Range {};
    class Cref;
    rcstring();
    rcstring(const char*);
    rcstring(const rcstring&);
    ~rcstring();
    rcstring& operator=(const char*);
    rcstring& operator=(const rcstring&);
    rcstring& operator+=(const rcstring&);
    rcstring operator+(const rcstring&) const;
    friend ostream& operator<<(ostream&, const rcstring&);
    void check(unsigned int i) const;
    char read(unsigned int i) const;
    void write(unsigned int i, char c);
    char operator[](unsigned int i) const;
    Cref operator[](unsigned int i);
    int atoi();
    rcstring toLower();
    rcstring Left(int n);
};

/////////////////POCZATEK STRUCT RCTEXT //////////////////
struct rcstring::rctext 
{
    char* s;
    unsigned int size;
    unsigned int n;

    rctext(unsigned int nsize, const char* p) //konstruktor
    {
        n = 1;
        size = nsize;
        s = new char[size + 1];
        strncpy(s, p, size); //skopiuje "size" znaków z p do s
        s[size] = '\0';
    };
    ~rctext() //destruktor
    {
        delete[] s;
    };
    rctext* detach() //funkcja ta odłącza odwołanie
    {
        if (n == 1)
            return this;
        rctext* t = new rctext(size, s);
        n--;
        return t;
    };
    void assign(unsigned int nsize, const char* p)
    {
        if (size != nsize) 
    {
            char* ns = new char[nsize + 1];
            size = nsize;
            strncpy(ns, p, size); //skopiuje "size" znaków z p do ns
            delete[] s;
            s = ns;
        }
        else
            strncpy(s, p, size);
        s[size] = '\0';
    }

private:
    rctext(const rctext&);
    rctext& operator=(const rctext&);
};
////////////////////KONIEC STRUCT RCTEXT ///////////////////////////
////////////////////POCZĄTEK CLASS CREF ////////////////////////////
class rcstring::Cref 
{
    friend class rcstring;
    rcstring& s;
    int i;
    Cref(rcstring& ss, unsigned int ii)
        : s(ss)
        , i(ii){};

public:
    operator char() const
    {
        cout << "operator char() const" << endl;
        return s.read(i);
    }
    rcstring::Cref& operator=(char c)
    {
        cout << "void operator = (char c)" << endl;
        s.write(i, c);
        return *this;
    }
    rcstring::Cref& operator=(const Cref& ref)
    {
        return operator=((char)ref);
    }
};
//////////////////KONIEC CLASS CREF ///////////////////////////
//kody wstawiane w miejsca wywołań
inline rcstring::rcstring()
{
    data = new rctext(0, "");
}

inline rcstring::rcstring(const rcstring& x)
{
    x.data->n++;
    data = x.data;
}
inline rcstring::~rcstring() //to ważne
{
    if (--data->n == 0) 
        delete data;
}

rcstring& rcstring::operator=(const rcstring& x)
{
    x.data->n++;
    if (--data->n == 0)
        delete data;
    data = x.data;

    return *this;
}

rcstring::rcstring(const char* s)
{
    data = new rctext(strlen(s), s);
}

rcstring& rcstring::operator=(const char* s)
{
    if (data->n == 1)
        data->assign(strlen(s), s);
    else 
    {
        rctext* t = new rctext(strlen(s), s);
        data->n--;
        data = t;
    };

    return *this;
}

ostream& operator<<(ostream& o, const rcstring& s) //wypisanie do konsoli
{
    return o << s.data->s;
}

rcstring& rcstring::operator+=(const rcstring& s)
{
    unsigned int newsize = data->size + s.data->size;
    rctext* newdata = new rctext(newsize, data->s); //do konstr
    strcat(newdata->s, s.data->s); //dopisanie na koniec tego co po prawej
    if (--data->n == 0) 
        delete data;
    data = newdata;
    return *this;
}

rcstring rcstring::operator+(const rcstring& s) const
{
    return rcstring(*this) += s;
}

inline void rcstring::check(unsigned int i) const
{
    if (data->size <= i) //ilość odwołań musi być większa od i, a jak nie to jest błąd
        throw Range();
}
inline char rcstring::read(unsigned int i) const
{
    return data->s[i];
}
inline void rcstring::write(unsigned int i, char c)
{
    data = data->detach();
    data->s[i] = c;
}

char rcstring::operator[](unsigned int i) const
{
    cout << "char rcstring::operator[](unsigned int i) const" << endl; 
    check(i); //check robi: if(data->size<=i) to throw Range() 
    return data->s[i];
}

rcstring::Cref rcstring::operator[](unsigned int i) // to co wyżej tylko, że to akurat jest używane w programie patrząc na konsole
{
    cout << "Cref rcstring::operator[](unsigned int i)" << endl;
    check(i);
    return Cref(*this, i);
}
//Początek dodawanych Modułów///////////
int rcstring::atoi()
{

return ::atoi(data->s);

}

rcstring rcstring::toLower()
{
    int i=0;
    rcstring test;   //Dodałem tymczasowy rcstring test
    test=data->s;  // skopiowałem do niego data->s 
    int size = strlen(test.data->s);  // tu już działam na test.data->s 
    char *c = new char[size];
    while(test.data->s[i]) // też na test
    {
    c[i] = tolower(test.data->s[i]); // też na test
    test.data->s[i]=c[i];
    i++;
    }

return test;  //tu chyba źle, bo samego obiektu nie powinienem raczej zwracać. 
}

rcstring rcstring::Left(int n)
{
    int size = strlen(data->s);
    if(n<0 || size <n)
    {
        throw Range();
    }
    char *c = new char[size-n];

    strncpy(c,data->s+n,size-n);

return c;
}

#endif /* __RCSTRING_H__ */
  1. Po co jest i co robi klasa Range{}?
    Jest to z tego co widzę pusta klasa i jest użyta tylko do

    inline void rcstring::check(unsigned int i) const
    {
    if (data->size <= i) //ilość odwołań musi być większa od i, a jak nie to jest błąd
        throw Range();
    }

    Czyli używamy jej tylko po to żeby działała na jakieś wyjątki?

  2. Co robi klasa Cref?
    Z tym generalnie bym prosił o największą pomoc, bo zadanie jest z tematu zliczania odwołań.
    Dziwię się dlaczego jest dwa razy napisany kod do operator=. Przyjmują różne argumenty co prawda ale czy nie będzie to stwarzało konfliktów?
    Próbowałem ogarnąć co się dzieje z tymi funkcjami write i read ale nie rozumiem tego w pełni. Gubię się jak dochodzi do tego funkcja detach() i nie wiem jaką rolę ta klasa pełni w tym kodzie
    Jakby ktoś miał czas to bardzo proszę o pomoc z rozumieniu.

  3. Dwie takie same funkcje / moduły do operator[]. Jeden z const, drugi bez i jeden z klasy Cref, a drugi z samej rcstring. Podejrzewam, że profesor zada jakieś pytanie odnośnie tego czym się te operator[] różnią. Jest tu coś ciekawego jeszcze poza tym co wymieniłem

    • jedno działa w klasie Cref, drugie w rcstring
    • jedno ma const czyli dane przyjmowane nie będą mogły być modyfikowane,a w drugim będą mogły
char rcstring::operator[](unsigned int i) const
{
    cout << "char rcstring::operator[](unsigned int i) const" << endl; 
    check(i); //check robi: if(data->size<=i) to throw Range() 
    return data->s[i];
}

rcstring::Cref rcstring::operator[](unsigned int i) // to co wyżej tylko, że to akurat jest używane w programie patrząc na konsole
{
    cout << "Cref rcstring::operator[](unsigned int i)" << endl;
    check(i);
    return Cref(*this, i);
}

No i chyba z resztą dam sobie radę. Jakby ktoś miał chwilę mi pomóc to bardzo dziękuję. Odwdzięczam się lajkami :)
Ps: Może to za dużo kodu jak na wrzucenie na forum. Wandbox: https://wandbox.org/permlink/m5CUvnsV7PcjYE4S

2

Ad 1,
Range to taka klasa pojemnikowa na wyjątek. Jak zrobisz konstrukcję:

rcstring s;
try
{
   s.check(2);//zakładamy, że 2 jest dość małe aby wykonało się throw Range()
}
catch (rcstring::Range &range)
{
    //tu jest złapany wyjątek typu Range, inne są przepuszczane dalej. Możesz używać obiektu range
}

takich klas do ciskania jako wyjątki używa się do przesłania informacji co poszło tak bardzo źle, że aż musiał polecieć wyjątek. W Twoim przypadku klasa Range() nie ma żadnych pól. ani metod, ale mogłaby mieć.

Ad 2.
Cref służy głównie to mącenia w głowie studentom przez kretyńsko skomplikowane odczytywanie/zapisywanie znaku z rctext *data.

Ad 3.
Góglasz za "c++ difference between const and nonconst function".
Generalnie, z punktu widzenia kompilatora const i non-const to zupełnie różne funkcje. O Swarogu, ile razy ja się na to łapałem jak się uczyłem Qt 4.x ...

0

Tutaj kod main:

#include "rcstring.h"

int main()
{
rcstring a,b,c,f("12345"),g("ASVASVASV"),z("STRINGJAKIS");
rcstring sec;
a="10";
b="ala ma kota";
cout << a << " " << b << endl; // 10 ala ma ma kota
c=a+b;
//Dodane atoi,tolower,left
double number=f.atoi();
rcstring G=g;
cout <<"TUTAJ G duzymi literami: "<< G <<endl;
rcstring lowered = g.toLower();
cout <<"TUTAJ G duzymi literami: "<< G <<endl;
rcstring Left = z.Left(5);
cout<<"+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"<<endl;
cout<<"+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"<<endl;
cout<<"FUNKCJA ATOI: "<<number<<endl;
cout<<"FUNKCJA toLower: "<<lowered<<endl;
cout <<"TUTAJ G duzymi literami powinno byc: "<< G <<endl;
cout<<"FUNKCJA LEFT: "<<Left<<endl;
cout<<"+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"<<endl;
cout<<"+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"<<endl;
cout << c<<endl; // 10ala ma kota
c=a+rcstring(" ")+b;
cout << c << endl; //10 ala ma kota
rcstring d("a");
cout << d <<endl; //a
d+="ula";
cout << d << endl; //aula
d+="15";
cout << d << endl; //aula15
cout << d[3]<<endl; //a
d[3]='b';
cout << d << endl; //aulb15
d[2]=d[1]=d[0];
cout << d << endl; //aaab15
return 0;
}
0
  1. Ok, kumam :D Ale zdziwiłem się, że dla jednego throw jest zrobiona specjalna klasa. Ale jak tak się robi to ok :D
  2. Profesor za takie wytłumaczenie klasy cref mi 0% wystawi za zadanie xD A na 80% o tą klasę zapyta - jak działa, po co itd. A jak zacznę sam mówić o tej klasie to już nie będzie się raczej zagłębiał dalej. Tak to czasem bywa :D
  3. "z punktu widzenia kompilatora const i non-const to zupełnie różne funkcje" ta wiedza mi się z pewnością przyda i teraz i w przyszłości więc ogromne dzięki.
    Co do wygólowania to z ciekawszych rzeczy wyczytałem tylko:
    bez const - możesz wywołać funkcję z możliwością modyfikacji żeby zmieniać wynik
    z const - możesz wywołać funkcję ale nie masz możliwości edycji wyniku więc sobie go po prostu podejrzysz po wywołaniu
    A drugie to że jeśli na stałym obiekcie zostanie wywołana funkcja to kompilator wykorzysta tę funkcję z const, a jak na nie stałym to tę drugą. I chyba o to by chodziło profesorowi jakby pytał :D

To prosiłbym o jeszcze wyłumaczenie tego cref. I mam też info o co pytał inne grupy już:

a).Kopiowanie płytkie głębokie różnice i jak użyte w kodzie
Góglowałem ale nic nie znalazłem. Lecę dalej góglować.
b). czemu Cref a nie char
Totalnie nie wiem o co chodzi :/
c) co sie dzieje w tym Cref
Nadal nie wiem :/
d) pytal o to czemu sa funkcje skladowe zadeklarowane, a nie zdefiniowane i co to daje
Chodzi o to, że definicje funkcji są napisane poza klasami, a w samej klasie są deklaracje? Bo jeśli tak to wiem, że to poprawia czytelność kodu + jeśli pisze się funkcje w klasie to trzeba uważać na to że, kompilator nie widzi funkcji, które są napisane wyżej. A jak napiszemy to wszystkie poza klasą to już tego problemu nie będzie
e) pytał o konstruktor inicjalizujacy i po co sie go uzywa
W naszym kodzie to chyba to:

class rcstring::Cref 
{
    Cref(rcstring& ss, unsigned int ii)
        : s(ss)
        , i(ii){};

Z tego co wyczytałem to jedyny sposób gdzie można wywołać konstuktor klas / obiektów. I można danej const nadać wartość początkową lub po prostu zainicjalizować jakieś zmienne. Dobrze to wyczytałem czy może jeszcze coś ważnego o tym trzeba wiedzieć?

3

Ogólnie jeśli chcesz się zgłębić w temat to szukaj pod hasłem implicit sharing lub copy-on-write
I zgadzam się z @MasterBLB. Wartość to wartość, referencja to referencja. Dla mnie osobiście ta technika jest nie warta świeczki i zaciemnia tylko sprawę.

e) pytał o konstruktor inicjalizujacy i po co sie go uzywa

Jeśli składową klasy jest referencja, to nie ma wyjścia, musi być zainicjalizowana zanim ruszy konstruktor, z prostego powodu: referencja nie może być null.

1
wiezaawieza napisał(a):

d) pytal o to czemu sa funkcje skladowe zadeklarowane, a nie zdefiniowane i co to daje

Co do czytelności to racja, co do umiejscowienia to nie prawda, ale jest jeszcze coś - otóż funkcje zdefiniowane od razu razem z deklaracją w klasie są traktowane jako inline.

Co do Cref - serio Bracie wiezaawieza, to jest klasa z d**y wzięta, a służąca profesorowi do oblewania studentów. Wydaje mi się, że robi za wyjątkowo szkaradnie napisany const wskaźnik na jakiś indeks łańcucha *char z rctext - przechowuje zmienną i której nie modyfikuje, i używa rcstring::read(i) i rcstring::write(i) które robią odczyt/zapis znaku.
Po jaki luj tak komplikować prostą operację, to naprawdę nie wiem. Chyba, że masz niepełne źródła?

wiezaawieza napisał(a):

e) pytał o konstruktor inicjalizujacy i po co sie go uzywa

Tutaj najprawdopodobniej profesor popłynął - właściwy termin to lista inicjalizacyjna konstruktora (noo, chyba że ja nigdy takiego określenia nie słyszałem, jak coś poprawcie mnie Bracia).
Zastosowania generalnie wymieniłeś dobrze, ale zapomniałeś, że jeszcze może wywoływać konstruktor klasy bazowej. Przykład z życia wzięty:

MechPartWidget::MechPartWidget(QWidget *parent)
:QWidget(parent), ui(new  Ui::MechPartWidget) // definicja klasy wygląda tak: class MechPartWidget : public QWidget. Poza tym jak widzisz można inicjalizować wskaźniki poprzez zastosowanie new
0

d) no ale w tym kodzie w klasie są tylko deklaracje więc funkcje nie będą traktowane jako inline. O coś innego musiało profesorowi chodzić jeszcze ;/ Ale sama wiedza o inline się przyda :D

Co do cref to kod jest jeden i ten sam dla każdego udostępniony przez profesora więc po prostu do uwalenia nas :D Co do działania to postaram się to zrozumieć ;d

e) na 100% kolega mi to źle napisał po prostu, bo w google też od razu pojawia się termin "lista inicjalizacyjna konstruktora".
A co do naszego kodu:

    Cref(rcstring& ss, unsigned int ii)
        : s(ss)
        , i(ii){};

tutaj chodzi o to, że konstruktor przyjmuje dwie wartości, a następnie jest inicjalizacja s i nadanie jej wartości z argumentu ss, a potem inicjalizacja i z wartością argumentu ii?

A jakiś pomysł o co chodzi z b)? (Czemu Cref, a nie char)

Dla Mastera:

10 ala ma kota
TUTAJ G duzymi literami: ASVASVASV
TUTAJ G duzymi literami: asvasvasv
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
FUNKCJA ATOI: 12345
FUNKCJA toLower: asvasvasv  //Tutaj g małymi literami
TUTAJ G duzymi literami: asvasvasv
FUNKCJA LEFT: GJAKIS
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
10ala ma kota
10 ala ma kota
a
aula
aula15
Cref rcstring::operator[](unsigned int i)
operator char() const
a
Cref rcstring::operator[](unsigned int i)
void operator = (char c)
aulb15
Cref rcstring::operator[](unsigned int i)
Cref rcstring::operator[](unsigned int i)
operator char() const
void operator = (char c)
Cref rcstring::operator[](unsigned int i)
operator char() const
void operator = (char c)
aaab15
2

Koledzy już Ci wrzucili trochę informacji.

Co do listy inicjalizacyjnej, możesz zabłysnąć informacją że bez względu na kolejność w tejże liście, atrybuty będą inicjowane w kolejności deklaracji w klasie. Np. jeśli w klasie jest pierwsze int a a później int b, to nawet jeśli na liście jest : b(12), a(10), najpierw inicjuje się a a później b. Trzeba z tym uważać jeśli są od siebie zależne (lepiej po prostu unikać zależności).

Dodatkowo, nazwy https://en.cppreference.com/w/cpp/language/identifiers sekcja "In declarations" pkt 2. Używanie podwójnego podkreślenia ("podłoga") jest zabronione.. ale już może nie chłostaj profesora taką informacją. Chcesz przecież zdać :)

Jak coś jeszcze mi w oko wpadnie, dopiszę..

O co za *.h we włączeniach? stdio.h już od baaardzo dawna włącza się jako cstdio i podobie string.h jako cstring ... znów może go nie chłoszcz...
Aajj... using namespace std w nagłówku... zabiłbym w projekcie...

2

toLower() działa zgodnie z oczekiwaniami - https://onlinegdb.com/r1RhmJfkV
Pozwoliłem sobie wyciachać z maina rzeczy, które mnie nie interesowały, a przesłaniały całokształt.

0

ad a)
Co do shallow copy i deep copy - https://stackoverflow.com/que[...]810/deep-copy-vs-shallow-copy
Rzecz się rozbiega o wskaźniki i referencje. Przy płytkiej kopii są kopiowane wszystkie składniki "na pałę", w tym adresy na jakie wskazują wskaźniki/referencje - czyli wskazują na ten sam obiekt. W przykładzie zalinkowanym dotyczy to składnika *pi. Btw, zachowaniem domyślnym jakie przyjmuje kompilator tworząc domyślny operator przypisania jest memberwise copy - czyli leci po wszystkich składnikach klasy i wywołuje ich konstruktory kopiujące, a dla typów prostych robi shallow copy.

ad b)
No już odpisałem. To jest serio taki potworek programistyczny (będący idealnym kandydatem do tego wątku), że my tutaj nie wiemy po co on jest, poza tym co w zalinkowanym poście napisałem.
Moja rada - odpal debugger ze śledzeniem krok po kroku i wchodzeniem do wnętrza funkcji, oraz podglądem stosu.

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