Sprzężenie zwrotne - Segmentation fault

0

Witam, męczę się już bardzo długo z wyciekiem pamięci w moim programie. Na wstępie powiem, że próbuje zrobić sprzężenie zwrotne w układzie logicznym który sam tworze. Podam fragmenty kodu oraz wskaże gdzie występuje problem:

class OperatorBinarny : public Sygnal
{
    public:
        OperatorBinarny(Sygnal *wejscie1, Sygnal *wejscie2);
        void zmienSygnal(Sygnal *syg1, Sygnal *syg2){ we1 = syg1; we2 = syg2; };
    protected:
        Sygnal *we1, *we2;

        bool wejscie1() const { if(we1) {return we1->wyjscie();} return false; };/*Zwraca wartosc wejscia do ukladu*/
        bool wejscie2() const { if(we2) {return we2->wyjscie();} return false; };
};

OperatorBinarny::OperatorBinarny(Sygnal *wejscie1, Sygnal *wejscie2)
    : we1(wejscie1), we2(wejscie2)
{
}

class BramkaOr : public OperatorBinarny
{
    public:
        BramkaOr(Sygnal *we1, Sygnal *we2);
        bool wyjscie() const { return wejscie1() || wejscie2(); };
};

BramkaOr::BramkaOr(Sygnal *we1, Sygnal *we2)
    : OperatorBinarny(we1, we2)
{}

/*Klasa główna:*/
....
delete *it;
*it = new BramkaOr(syg_tmp, syg_tmp1);/*Nadpisanie pola wektora*/
....
BramkaOr *tmp = dynamic_cast<BramkaOr*>(*it);
            tmp->zmienSygnal(sygnaly[w1], sygnaly[w2]);
            cout << "wartosc: " << tmp->wyjscie() << endl;
....

Problem jest podczas wywołania funkcji wyjscie(), dokładnie debugger wskazuje to miejsce [kod=c++]bool wejscie1() const { if(we1) {return we1->wyjscie();} return false; };[/kod]

Generalnie wszystkie sygnały przechowuje w wektorze, i pomysł mam taki żeby wejścia które są wyjściami jeszcze nie określonych bramek inicjować wartością logiczną false, a po wczytaniu całego układu zmieniać poszczególne sygnały na te już zdefiniowane (bramki). Tyle tylko, że po zmianie sygnału w bramce na nowy mam wyciek pamięci.
Z góry dziękuję za wszelką pomoc, w razie potrzeby mogę wrzucić więcej kodu jakby ktoś chciał się zgłębić w mój problem.

1
  1. Dodaj do OperatorBinarny: virtual bool wyjscie() const=0; wtedy możesz od razu dawać: it->zmienSygnal(...); cout << "wartosc: " << it->wyjscie() << endl; bez tmp i dynamic_cast
  2. nie podałeś czym jest syg_tmp - podejrzewam że jest to nie zainicjalizowany wskaźnik.
0

virtual bool wyjscie() const=0; posiadam już w klasie Sygnal z której dziedziczę. syg_tmp</code> to de facto <code>sygnaly[w1] gdzie w1 to numer wejścia do bramki. Nawet samo wczytanie bez robienia jakichkolwiek konwersji powoduje błędy gdy mam sprzężenie zwrotne. Nie mogę tego rozwiązać przez cały dzień. Dzięki za pomoc.

1

virtual bool wyjscie() const=0; nie wiem po kiego ci to w Sygnal, ja wytłumaczyłem czemu to jest potrzebne w OperatorBinarny.
Więc pokaż jak to twoje de facto wygląda.

0
/*Funkcja bazowa*/
class Sygnal{
    public:
        virtual bool wyjscie() const = 0;
};

/*Kolejna klasa*/
class SygnalWejsciowy : public Sygnal
{
    private:
        bool sygnal;
    public:
        SygnalWejsciowy(bool a = false);
        void ustaw() {sygnal = true;};
        void wyczysc() {sygnal = false;};
    protected:
        bool wyjscie() const {return sygnal;};
};

/*Klasa glowna*/
    sygnaly.push_back(new SygnalWejsciowy(false));/*ZERO*/
    sygnaly.push_back(new SygnalWejsciowy(true));/*JEDEN*/

    /*Wczytujemy liczbe bramek*/
    for(int i=0;i < liczba_bramek;i++){
        sygnaly.push_back(new SygnalWejsciowy(false));/*Tworze sygnaly dla sieci domyslnie jako "false"*/
    }
...
        if(typBramki == "AND"){
            *it = new BramkaAnd(sygnaly[w1], sygnaly[w2]);/*Nadpisanie pola wektora*/
        }
        else if(typBramki == "XOR"){
            *it = new BramkaXor(sygnaly[w1], sygnaly[w2]);/*Nadpisanie pola wektora*/
        }
        else if(typBramki == "NOR"){
            *it = new BramkaNor(sygnaly[w1], sygnaly[w2]);/*Nadpisanie pola wektora*/
        }
        else if(typBramki == "OR"){
            *it = new BramkaOr(sygnaly[w1], sygnaly[w2]);/*Nadpisanie pola wektora*/
        }
        else if(typBramki == "NOT"){
            *it = new BramkaNot(sygnaly[w1]);/*Nadpisanie pola wektora*/
        }
/*it - to iterator po wektorze sygnaly*/

void SiecLogiczna::wyprowadzWyniki(){
    int i = 0;
    for(vector<Sygnal *>::iterator it = sygnaly.begin()+2;it < sygnaly.end(); ++it){
        cout << typ_bramki[i] << "-->" << (*it)->wyjscie() << endl;/*Wypisuje wyjscia ukladu*/
        ++i;
    }
}

Po wywołaniu funkcji wyprowadzWyniki() dostaje blad (ale tylko wtedy gdy mam sprzezenie zwrotne) np. na wejscie bramki 1 daje wejscie bramki 2 a na wejscie 2 daje 1). Jeśli masz jakiś pomysł będę wdzięczny.

1

wypełniasz wektor sygnaly przez push_back() wrzucając tam new SygnalWejsciowy(false) póznej nadpisujesz ten wskaźnik: *it = new BramkaAnd(sygnaly[w1], sygnaly[w2]);
więc masz tu wyciek pamięci bo gubisz i odpowiednio nie zwalniasz to: new SygnalWejsciowy(false);
zaś jeżeli zrobisz zapętlenie w swojej strukturze to podłączasz się pod coś co wcześniej zgubiłeś lub zgubisz później.
Masz źle zorganizowaną hierarchie klas.

0

Dzięki, akurat nie dokleiłem do tego kodu co w/w ale mam oczywiście przed zarezerwowaniem nowej pamięci w wektorze: delete *it;/*Zwalniam pamięć elementu w wektorze, aby go ząstapic nowym*/ więc to nie tutaj jest wyciek ale tak jak mówisz muszę coś gubić po drodze gdy mam sprzężenie. Czy mógłbyś mnie jakoś nakierować jak mogłaby wyglądać hierarchia klas dla takiego problemu?

1

Właśnie tutaj.
Przy zapętleniu robisz:
S3=new COS(S4,S0);
delete S4;
S4=new COS(S3,S0);
więc wejście pierwsze od S3 wskazuje na zwolnioną pamięć.

0

Ok to już wszystko jasne dzięki wielkie za pomoc, dalej jednak nie mam pomysłu ja to można obejść.

1

To proste nie możesz w konstruktorze podpinać wejść.
Zrób podpięcie w drugą stronę wejście podpinasz do bramki.
Niech bramka ma listę wejść na które ma wysłać sygnał w postaci wektora struktur {Bramka, numer wejścia).
Stwórz klasę PłytaGłowna która trzyma listę bramek oraz listę zmian {Bramka,numer wejścia, ZBramki}
Jak bramka zmienia stan to dodaje do listy zmian PlytyGlownej całą swoją listę {Bramka,numer wejścia, this}
PłytaGłowna wykonuje robi kopie listy zmian, zeruje listę zmian, wykonuje zmiany zapisane w kopii.
Po wykonaniu zmian może się okazać że lista zmian już jest nie pusta (w przypadku sprzężenia zwrotnego) więc znowu ją wykonuje.
Oczywiście na wykonanie listy zmian potrzebny osobny wątek.

1

A co chcesz zrobić w przypadku sprzężenia zwrotnego? Czy chcesz, aby układ zaczął oscylować czy może chcesz odrzucić taki układ i wyświetlić komunikat użytkownikowi, że jest sprzężenie i dupa z tego wszystkiego? (to drugie będzie dużo prostsze)

0

A więc tak, nie mogę niestety w tym projekcie korzystać z wątków, a więc to odpada. Sprzężenie zwrotne realizować muszę, ale mogę to robić np. tak że układ będzie się dopiero po paru przebiegach stabilizował, tzn. tak jak wcześniej pisałem mogę ustalać najpierw domyślnie wartości dla sprzężeń, a potem dopiero robić kolejne przebiegi, aż do momentu w którym stany będą zgodne z tym czego oczekuję. Okazało się też, że problem jest w tym, że funkcje wejscie1(), wejscie2() i wyjscie() wywolują się wzajemnie co powoduje zapętlenie więc muszę to poprawić. Pomysł mam taki żeby zrobić możliwość zmiany sygnałów w klasie Bramka i ustawiać je w zależności od potrzeby. Jeśli macie jeszcze jakieś pomysły/uwagi będę bardzo wdzięczny.

1

Zdefiniuj sobie czas reakcji (czas od momentu zmiany stanu na wejściu do momentu w którym zmieni się stan na wyjściu). Każdą zmianę na danym jednym wejściu wrzuć do kolejki priorytetowej. Niech priorytetem będzie czas w jakim bramka zareaguje (nie "za ile zareaguje" tylko "kiedy zareaguje", taki czas bezwzględny). Dopóki kolejka jest niepusta przetwarzaj wejścia zgodnie z priorytetem. Ilość takich przetwarzań musi być ograniczona coby nie wpaść w nieskończoną pętlę.

Nie jest to perfect rozwiązanie, ale jakieś jest.

//EDIT
Jeśli by przyjąć stały i identyczny czas reakcji dla każdej bramki to rozwiązanie pokryje się z tym zaproponowanym przez @_13th_Dragon. Z tym, że po głowie chodziło mi właśnie aby ten czas nie był zawsze ten sam stąd propozycja kolejki priorytetowej. Można by wprowadzić niewielki element losowości (np. rzędu promila). Wtedy układ powinien częściej się stabilizować.

0

Dzięki za dotychczasową pomoc, zastosowałem się do waszych sugestii i zrobiłem projekt od nowa ale mam znowu problemy typu Segmentation Fault i nie wiem dlaczego, może ktoś z was powie co jest nie tak i czy to co teraz napisałem będzie ok dla sprzężenia zwrotnego:

class Bramka
{
    protected:
        Wyjscie *wyjscie;
        Wejscie *we1, *we2;
    public:
        virtual void ustawWyjscie() { };
        Bramka(Wejscie *wejscie1);
        Bramka(Wejscie *wejscie1, Wejscie *wjescie2);
        void podlacz(Wejscie *we){ wyjscie->zbiorWejsc.push_back(we); }/*Dodaje wejscie do ktorego podpinam wyjscie*/
        Wejscie* pobierzWejscie1() const { return we1; };
        Wejscie* pobierzWejscie2() const { return we2; };

};

class Wyjscie : public Sygnal
{
    public:
        Wyjscie();
        std::list <Wejscie*> zbiorWejsc;
        void zmienStan(bool nowyStan) { sygnal = nowyStan; };/*Ustalam nowa wartosc sygnalu*/
        void ustawWejscia();/*Podaje sygnal na wejscia polaczone z wyjsciem*/
};

class Wejscie : public Sygnal
{
    public:
        Wejscie(bool sygnal);
        void zmiana(bool nowy_sygnal) { sygnal = nowy_sygnal; };
};

class BramkaAnd : public Bramka
{
    public:
        BramkaAnd(Wejscie *wejscie1, Wejscie *wejscie2);
        void ustawWyjscie();
};

void BramkaAnd::ustawWyjscie() {
    bool stanWyjscia = we1->pobierzSygnal() && we2->pobierzSygnal();
    wyjscie->zmienStan(stanWyjscia);
    wyjscie->ustawWejscia();/*Przekazuje nowy sygnal na wejscia bramek do ktorych podlaczylem wyjscie tej bramki*/
};

/*... istotniejsza czesc klasy SiecLogiczna*/

        /*Sprawdzanie typu bramki i dodawanie jej do listy*/
        if(typBramki == "AND"){
            nowaBramka = new BramkaAnd(we1, we2);
        }
        else if(typBramki == "XOR"){
            nowaBramka = new BramkaXor(we1, we2);
        }
        else if(typBramki == "NOR"){
            nowaBramka = new BramkaNor(we1, we2);
        }
        else if(typBramki == "OR"){
            nowaBramka = new BramkaOr(we1, we2);
        }
        else if(typBramki == "NOT"){
            nowaBramka = new BramkaNot(we1);
            w2 = 0;
        }
        else{
            cout << "Blad nazwy bramki." << endl;
            break;/*Przerwanie petli for*/
        }
        bramki.push_back(nowaBramka);/*Umieszczam na stosie nowa bramke*/

        /*Dodaje do listy wejscia wszystkich bramek*/
        sWejscia tmpWejscia;
        tmpWejscia.we1 = w1;
        tmpWejscia.we2 = w2;
        wejscia_bramek.push_back(tmpWejscia);
    }/*for*/

    /*Dodaje wejscia do podpiecia wyjsc bramek*/
    for(int i=0;i < liczba_bramek;++i){
        if(wejscia_bramek[i].we1 > 1){/*Jesli jest to wejscie innej bramki*/
            cout << wejscia_bramek[i].we1 << endl;
            bramki[wejscia_bramek[i].we1-2]->podlacz(bramki[i]->pobierzWejscie1());/*Podlaczam wyjscie bramki o indeksie we1 do wejcia bramki[i]*/
        }
        else if(wejscia_bramek[i].we2 > 1){/*Jesli jest to wejscie innej bramki*/
            cout << endl << wejscia_bramek[i].we2-2 << endl;
            cout << i;
            bramki[wejscia_bramek[i].we2-2]->podlacz(bramki[i]->pobierzWejscie2());/*To samo podlaczenie tylko dla we2*/
        }
    }/*for*/

    plik.close();
    return;
}

void SiecLogiczna::iterujUklad(){
    for(vector<Bramka*>::iterator it = bramki.begin(); it != bramki.end();++it){
        (*it)->ustawWyjscie();
    }
}

 

Błąd jest podczas wywoływania funkcji podlacz(), dokładnie gdy próbuję dodać do listy wejście układu w klasie Wyjscie. Mam też pytanie odnośnie funkcji ustawWyjscie() która u mnie jest wirtualna, czy takie rozwiązanie zadziała, że będzie wywoływana funkcja ta która jest zdefiniowana w klasie konkretnej bramki czy to tak nie działa (a jeśli nie to jak to inaczej zrealizować)?

1

Czym jest we1 i we2 w nowaBramka = new BramkaAnd(we1, we2); ?

0
Wejscie *JEDEN = new Wejscie(true);
Wejscie *ZERO = new Wejscie(false);
/*Okreslam poczatkowe sygnaly wejsciowe*/
if(w1 == 0)
    we1 = ZERO;
else if(w2 == 0)
    we2 = ZERO;
else if(w1 > 0)
    we1 = JEDEN;
else if(w2 > 0)
    we2 = JEDEN;

Co do błędu to poprawiłem nieco kod i zrobiłem w klasie Bramka pole Wyjscie wyjscie bez wskaźnika, ale dalej jest to samo, jak podam jakieś nowe wejscie Wejscie *nowe = new Wejscie(false); to też jest to samo.

0

Pierwszy problem już poprawiłem. Został mi jeszcze ten drugi tzn. program zawiesza się na funkcji pobierzSygnal() w klasie Sygnal podczas iterowania ukladu tzn. po wywołaniu funkcji, ustawWyjscie(), która to wywołuje właśnie pobierzSygnal(), jak w zmieniłem ciało funkcji np. na return false; to działa ok. Jakieś pomysły?

1

else if(wejscia_bramek[i].we2 > 1) // ten else jest niepotrzebny, if owszem
Jak to nie pomoże to użyj debuger'a lub podawaj cały kod.

0

Dokładnie tak! Błąd logiczny powodował to, że "we2" nie wskazywało na żadne "Wejscie", wystarczyło zamiast else if dać same if'y. Naprawdę jestem pełen podziwu za twoje bezinteresowne zaangażowanie w ten temat. WIELKIE DZIĘKI!

1

Zastanów się nad pewnymi zmianami.

  1. Każda bramka ma nazwę np "T1.1", "T1.2"
  2. Zamiast wektora użyj mapy mapującej nazwę na wskaźnik do bramki.
  3. Każda bramka oprócz Not i Pin może mieć więcej niż jedno wejście.
  4. Stwórz klasę Układ lub Płyta reference do której będzie miała każda bramka ta klasa zawiera mapę.
    Przy takim układzie tworzenie Bramki będzie wyglądało następująco:
Uklad U;
new BramkaNot(U,"A","C"); // bramka o nazwie "A" z wejściem z bramki "C" jeszcze nie istniejącej
new BramkaAndNot(U,"B","A;D;D"); // bramka o nazwie "B" z trzema wejściami podpiętymi do bramek "A", "D" i znowu "D". "A" już się podepnie bo istnieje, zaś "D" się nie podepnie.
new BramkaOrNot(U,"C","B;E"); // Teraz powstała bramka "C", więc układ przy jej dodaniu przeleci wszystkie bramki i wszystkie wejścia znajdzie odwołanie do bramki "C" i podepnie go.
new BramkaPin(U,"D"); // podepnie się pod wszystkie wejścia o nazwie D, BramkaPin ma ręczne ustawienia sygnału - publiczne.
new BramkaPin(U,"E"); // podepnie się pod wszystkie wejścia o nazwie D, BramkaPin ma ręczne ustawienia sygnału - publiczne.
//   new BramkaXor(U,"A","B"); // wygeneruje wyjątek - bramka A już istnieje.
U["D"]=true; // ustawia wyjście D na true; operator [] zwraca referencje do bramki o nazwie "D" przez tą samą mapę operator=(bool) dla bramki zmienia jej stan
U["E"]=false; // ustawia wyjście E na true;
// U["A"]=true;  // wygeneruje wyjątek - bramka nie jest wejściem układu
// W konstruktorze każdej bramki zmienna LicznikZmian=0;
bool F=U.Tick(); // obliczasz każdą bramkę i jeżeli bramka zmienia wyjście to zwiększasz LicznikZmian, metoda Tick zwraca czy przynajmniej jedna bramka zmieniła stan.
F=U.Go(); // metoda zeruje LicznikZmian=0; oraz powtarza U.Tick(); dopóki kolejny U.Tick(); nie zwróci false lub LicznikZmian jakieś bramki nie osiągnie wartości 2. Przy LicznikZmian=2 zwrac false
cout<<U["C"]; // zadziała operator konwersji bramki na bool: operator bool()const {} który zwróci aktualny stan wyjścia "C"

Przy takiej organizacji wszystko będzie proste i przezroczyste, ba nie potrzebujesz takich klas jak wejście lub wyjście lub nawet sygnal.

0

Ogólnie za dużo klas, za dużo wskaźników, za dużo obiektów tworzonych dynamicznie. Ja bym to oparł mniej więcej na takim zestawie klas:

class Element;

struct Wejscie {
    Element *element;
    unsigned pin;
};

struct ZmianaStanu {
    Wejscie wejscie;
    bool nowy_stan;
};

class Plyta;
{
private:
    std::map<std::string, Element*> elementy; // zbior wszystkich elementów, mapuje nazwę elementu na element
    std::map<unsigned long long, ZmianaStanu> zmiany_stanow; // kolejka priorytetowa zmian

public:
    class Kreator {
    private:
        Plyta *plyta;
    
    public:
        Kreator();    
    
        void NowaPlyta();
    
        void DodajElement(std::string typ, std::string nazwa);
        void Polacz(std::string element_od, unsigned pin_od, std::string element_do, unsigned pin_do);

        Plyta *ZbudujPlyte();
    };

    void PowiadomOZmianieNaWyjsciu(Element *element, unsigned pin); // element informuje o zmianie stanu na swoim wyjsciu

    Element *operator [] (std::string nazwa); // zwraca element o danej nazwie
    
    void Uruchom(unsigned maksymalna_ilosc_iteracji);
};

class Element
{
private:
    friend class Plyta;
    friend class Plyta::Kreator;

    Plyta *plyta;

    Wejscie polaczenia_wyjsc[ilosc_wyjsc]; // wskazania wejść z którymi dane wyjścia są połaczone
    bool stan_wyjsc[ilosc_wyjsc];          // aktualny stan na wyjsciach
    bool stan_wejsc[ilosc_wejsc];          // aktualny stan na wejsciach

protected:
    // ustawia stany na danym wyjsciu, do użytku przez UaktualnijStan
    void UstawStanWyjscia(unsigned pin, bool wartosc)
    {
        if (stan_wyjsc[pin] != wartosc)
        {
            stan_wyjsc[pin] = wartosc;
            if (plyta != NULL) plyta->PowiadomOZmianieWyjscia(this, pin);
        }
    }

    // ustawia stany na wyjsciach zgodnie ze stanami na wejsciach
    virtual void UaktualnijStan() = 0;

public:
    const unsigned ilosc_wejsc;
    const unsigned ilosc_wyjsc;
    const unsigned czas_reakcji;
    
    Element(unsigned ilosc_wejsc, unsigned ilosc_wyjsc, unsigned czas_reakcji);
    
    bool StanWyjscia(unsigned pin);
    bool StanWejscia(unsigned pin);
};

// dalej implementacje konkretnych elementów
// ...
0

@adf88, chodzi mi o to że ja w ramach ćwiczeń póki leżę chory napisałem ten projekt, main() wygląda tak:

bool more(Core &U,bool nowait)
  {
   U.outstates(cout);
   if(nowait)
     {
      cout<<endl;
      return true;
     }
   string stop;
   cout<<" *-stop: ";
   getline(cin,stop);
   return !stop.length();
  }

int main()
  {
   SetConsoleOutputCP(1250);
   Core U;
   try
     {
      GatePin &pa=*(new GatePin(U,"A"));
      GatePin &pb=*(new GatePin(U,"B"));
      new GateAndNot(U,"x1:A;x3");
      new GateOrNot(U,"x2:B;x1");
      new GateNot(U,"x3:x2");
      cout<<"Schemat:"<<endl<<U<<endl<<endl;
      while(true)
        {
         int x;
         cout<<"1. Zmień A"<<endl;
         cout<<"2. Zmień B"<<endl;
         cout<<"3. Symulacja"<<endl;
         cout<<"0. Koniec"<<endl;
         cout<<"Wybór: ";
         if(cin>>x)
           {
            cin.sync();
            if(x==1)
              {
               U.outnames(cout)<<endl;
               more(U,true);
               pa=!pa;
               more(U,true);
              }
            else if(x==2)
              {
               U.outnames(cout)<<endl;
               more(U,true);
               pb=!pb;
               more(U,true);
              }
            else if(x==3)
              {
               U.outnames(cout)<<endl;
               U.Go(3,more);
              }
            else if(!x) break;
            else
              {
               cout<<"Niepoprawny wybór"<<endl;
               continue;
              }
           }
         else
           {
            cin.clear();
            cin.sync();
            cout<<"Błąd wprowadzania"<<endl;
           }
         cout<<endl;
        }
     }
   catch(const Gate::ExceedInputMin&) { cout<<"ExceedInputMin"<<endl; cin.sync(); cin.get(); }
   catch(const Gate::ExceedInputMax&) { cout<<"ExceedInputMax"<<endl; cin.sync(); cin.get(); }
   catch(const Gate::EmptyName&) { cout<<"EmptyName"<<endl; cin.sync(); cin.get(); }
   catch(const Core::DuplicateName&) { cout<<"DuplicateName"<<endl; cin.sync(); cin.get(); }
   catch(const Core::GateNotExists&) { cout<<"GateNotExists"<<endl; cin.sync(); cin.get(); }
   catch(...) { cout<<"Unknown exception"<<endl; cin.sync(); cin.get(); }
   return 0;
  }

Plus 350 wierszy modułu Core + Gate*

0

Hmmm. Dla sportu tez może bym machnął jakąś implementację :)

Aby to wszystko było funkcjonalne proponuję następujące warunki (do uzgodnienia):

  • na wejściu dostajemy listę elementów oraz połączeń między nimi
  • program ma zbudować układ i poddać go walidacji
  • na wyjściu wypluć stany na elementach kontrolnych
  • wsparcie dla elementów z wieloma wyjściami
  • brak ograniczeń na ilość elementów
  • możliwość podania elementów/połączeń w dowolnej kolejności

ogólny format elementu/połączenia:
<typ elementu> [<nazwa elementu>] [<parametry elementu>]

połączenia:
CONNECT <nazwa elementu>:<numer pinu wyjściowego> <nazwa elementu>:<numer pinu wejściowego> [<nazwa elementu>:<numer pinu wejściowego> [...]]
np.
CONNECT A:1 B:2 (od A:1 do B:2)
CONNECT C:1 X:0 Y:0 (od C:1 do X:0 i Y:0)

bramki:
<typ elementu> <nazwa elementu>
np.
AND A
zbiór wspieranych bramek: AND, OR, XOR, NAND, NOR, NXOR, NOT

prosty demultiplekser (coby było coś z wieloma wyjściami i parametrami)
DEMUX <nazwa elementu> <ilosc linii adresowych> <ilosc linii wyjsciowych>
np.
DEMUX D 1 2
numeracja pinów wejściowych: pin 0 to sygnał wejściowy, piny 1..N to linie adresowe, brak pinu "strobe"

element wejściowy (zapodajemy sygnał):
INPUT <nazwa elementu> <wartość>
np.
INPUT I1 1
INPUT I2 0

element wyjściowy (kontrolny):
OUTPUT <nazwa elementu>
np.
OUTPUT O

Dla uproszczenia proponuję aby sama walidacja nie musiała wskazywać co i gdzie jest źle. Ma tylko sprawdzić czy wszystko jest OK lub nie.

Co ty na to?

//EDIT
Aha, jeszcze jedno. Brak możliwości kombinowania połączeń np.
CONNECT A:0 B:0
CONNECT A:0 C:0
ma zwrócić błąd (bo dwa razy podpinamy się pod A:0)

0

Czy nie wystarczy:
Bramki: AND, OR, XOR, NAND, NOR, NXOR, NOT, PIN
AND, OR, XOR, NAND, NOR, NXOR - ilość wejść 2..UINT_MAX liczy ilość wejść automatycznie.
NOT - ilość wejść 1..1
PIN - ilość wejść 0..0
Wprowadzenie plik1:
Typ Nazwa:[[[[Wejście1Nazwa];Wejście2Nazwa];Wejście3Nazwa];Wejście4Nazwa] ...
...
Np:
AND A:B;C;D
NOT B:A
PIN C
PIN D
Tak załatwia się cały schemat, nie musisz wskazywać do którego wejścia się podpinasz.
Na początku wszystkie piny są ustawione na 0
Taka konstrukcja danych nie wymusza podawania numeru wejścia, a stąd mniejsza ilość możliwych błędów.

Dane wejściowe plik2:
NazwaPina wartość max_ilość_tików
...
max_ilość_tików nie więcej niż tyle razy przeliczyć schemat, wcześniej kończymy jeżeli się ustabilizował
(max_ilość_tików może być 0 - nic nie liczymy - tak jakby dzieje się jednocześnie z następnym)
Np:
C 0 1
C 1 0
D 1 10
A 0 0
C 0 10

Dla danych wyjściowych NIE MA co podawać co chcemy kontrolować, bo zwyczajnie możemy dać wszystko jak leci.
Dane wyjściowe Plik3 (dla powyższych przykładów):
A B C D // nazwy
0 1 0 0 // po C 0 1
0 1 1 0 // po C 1 0
1 0 1 1 // po D 1 10
0 1 1 1
1 0 1 1
0 1 1 1
1 0 1 1
0 1 1 1
1 0 1 1
0 1 1 1
1 0 1 1
0 1 1 1 // 10 - ty
ERR // A 0 0 - Błąd bo A nie jest pinem
1 0 0 0 // po C 0 10, stan się ustabilizował więc nie powtarzamy

EDIT:
Albo jeszcze prościej:
Ja to dla sportu już napisałem, zrób na podstawie swoich założeń taki sam schemat i takie same działania:
cout<<"1. Zmień A"<<endl; // zmiana wartości pinu A na przeciwny, wyświetla nazwy i wartości dla wszystkich bramek wraz z pinami przed i po zmianie
cout<<"2. Zmień B"<<endl; // zmiana wartości pinu B na przeciwny, wyświetla nazwy i wartości dla wszystkich bramek wraz z pinami przed i po zmianie
cout<<"3. Symulacja"<<endl; // wyświetla nazwy i wartości dla wszystkich bramek wraz z pinami przed startem robi kolejne obliczenia jeżeli żadna bramka nie zmieniła wartości to na tym kończy. Jeżeli zmieniła się to wyświetla kolejny stan i czeka na wprowadzenie wiersza, przy podaniu pustego wiersza wykonuje kolejny krok, przy podaniu niepustego koniec.
cout<<"0. Koniec"<<endl; // koniec programu

EXE masz na PW.

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