Funkcje wywoływane przez uzytkownika

Funkcje wywoływane przez uzytkownika
0

Mam pytanie jeśli np mam w programie 20 funkcji i każda przyjmuje jakiś tam parametr i użytkownik może je wywołać za pomocą wpisania jej nazwy i parametru, na przykład:
Czekaj 20
i wtedy ma zostać wywołana funkcja Czekaj z parametrem 20. No i mamy te 20 funkcji no to pisanie cały czas if string == nazwa_funkcji i sprawdzanie tak każdej jest pewnie nie najlepszym rozwiązaniem. Czy może zastosowanie tablicy wskaźników na funkcji jest lepszym i poprawnym rozwiązaniem? Na zasadzie porównuje string'a z tablicą wskaźników i jeśli się zgadzają nazwy to ją wywołuje z parametrem?

Z góry dziękuje za wszystkie odpowiedzi.

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

Możesz zrobić sobie mapę odwzorowującą string na wskaźnik do funkcji (o ile funkcje mają ten sam typ!).


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
0

Załóżmy, ze wszystkie zwracają int, ale przyjmują różne typu parametry, wtedy też to zadziała?

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

Nie bo ich typy są różne, chyba ze wepchniesz je do jakiejś unii, ale tam znów musiałbyś mieć ify, więc odpada.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
KR
  • Rejestracja:prawie 16 lat
  • Ostatnio:5 miesięcy
  • Postów:2514
1

mozesz przerobic kod w taki sposob ze bede przyjmowac jako parametr stos (np ten z stl'a), w kazdej funkcji bedziesz wiedzial jak interpretowac dane na stosie, a parametry bedziesz mogl wrzucac np na podstawie tego stringa ktory przerabiasz. wyjdzie Ci z tego prosty interpreter w zasadzie


░█░█░█░█░█░█░█░█░█░█░█░
0

krwq mógłbyś trochę dokładniej wyjaśnić swój pomysł? Mógłbym to zrobić na zasadzie ifów cały czas ale tak np z 30 if'ów to pewnie nie jest za dobre rozwiązanie.

KR
  • Rejestracja:prawie 16 lat
  • Ostatnio:5 miesięcy
  • Postów:2514
0

masz przykład, przed chwilą napisałem na szybko:

Kopiuj
#include <iostream>
#include <stack>
#include <string>
#include <map>

#define ASSIGN(a) trans[#a] = a;

typedef std::stack<int> stack;
using std::cin;
using std::cout;
using std::string;
using std::endl;
typedef int (*func)(stack);
typedef std::map<string,func> transmap;
transmap trans;
bool error = false;
bool terminate = false;

int suma(stack s)
{
  int sum = 0;
  while (!s.empty())
   {
    sum+=s.top();
    s.pop();
   }
  return sum;
}

int roznica(stack s)
{
  if (s.size()!=2)
   {
     cout << "Zla ilosc parametrow!\n";
     error = true;
     return 0;
   }
  // trzeba pamietac ze parametry na stosie sa w odwrotnej kolejnosci
  int sum = -s.top();
  s.pop();
  sum+= s.top();
  return sum;
}

int wypisz(stack s)
{
  cout << "Ta funkcja nic nie robi poza wypisaniem tego tekstu.\n";
  return 0;
}

int koniec(stack s)
{
  terminate = true;
}

// http://www.codeproject.com/KB/stl/stdstringtrim.aspx
void trim(string& str)
{
  string::size_type pos = str.find_last_not_of(' ');
  if(pos != string::npos) {
    str.erase(pos + 1);
    pos = str.find_first_not_of(' ');
    if(pos != string::npos) str.erase(0, pos);
  }
  else str.erase(str.begin(), str.end());
}

// nie sprawdza czy liczba nie jest za dluga
bool try_convert(string& s, int& num)
{
  //cout << "debug: try_convert: s=XX" << s << "XX" << endl; // <-- zeby bylo biale znaki widac
  trim(s);
  //cout << "debug: try_convert: s=XX" << s << "XX" << endl;
  if (s.size()==0)
    return false;

  const char* p = s.c_str();
  bool neg = false;
  if (*p == '-')
   {
    neg=true;
    ++p;
   }
  num = 0;
  while (*p)
   {
    if (*p<'0' || *p>'9')
      return false;
    num *= 10;
    num += *p - '0';
    ++p;
   }
  if (neg)
    num = -num;
  return true;
}

bool call_func(const string& s)
{
  string func_name;
  string func_params;
  size_t p = s.find_first_of('(');
  size_t r = s.find_last_of(')');
  if (p==string::npos || r==string::npos || p>r)
    return false;

  func_name = s.substr(0,p);
  func_params = s.substr(p+1, r-p-1);
  trim(func_name);
  //cout << "debug:   func_name=" << func_name << endl;
  //cout << "debug: func_params=" << func_params << endl;

  transmap::iterator it = trans.find(func_name);
  if (it==trans.end())
    return false;
  //cout << "debug: doszedlem tu" << endl;

  stack st;
  string param;
  int par;
  while (1) // func_params.size()!=0
   {
    p = func_params.find_first_of(',');
    if (p==string::npos)
     {
      //cout << "debug: func_params=" << func_params << endl;
      trim(func_params);
      if (func_params.size()==0)
        break;
      if (!try_convert(func_params, par))
        return false;
      st.push(par);
      break;
     }
    param = func_params.substr(0, p);
    //cout << "debug: param=" << param << endl;
    if (!try_convert(param, par))
      return false;
    st.push(par);
    func_params = func_params.erase(0, p+1);
   }

  int ret = it->second(st);
  cout << "Funkcja zwrocila: " << ret << endl;
  if (error)
    return false;

  return true;
}

int main()
{
  ASSIGN(suma);
  ASSIGN(roznica);
  ASSIGN(wypisz);
  ASSIGN(koniec);

  string s;

  while(!terminate)
   {
    cout << "> ";
    std::getline(cin, s, '\n');
    if (!call_func(s))
      break;
    cout << endl;
   }
  if (!terminate)
    cout << "\n\nProgram zakonczyl sie, bo:\npodales nieprawidlowa nazwe funkcji,\nzle ja wywolales\nlub wystapil blad w funkcji!" << endl;

  return 0;
}

po włączeniu wpisujesz jedną z nazw funkcji:

Kopiuj
suma(1,2,3,4)
roznica(7,5)
wypisz()
koniec()

suma moze miec dowolna liczbe parametrow: liczby calkowite (int)
roznica musi miec dokladnie 2 parametry
wypisz moze miec dowolna liczbe parametrow bo je ignoruje
koniec dow. liczba parm. - wylacza program

zla nazwa funkcji lub nieprawidlowe argumenty spowoduja wylaczenie programu (celowe). funkcje ladnie widac jak dodawac samemu.


░█░█░█░█░█░█░█░█░█░█░█░
edytowany 1x, ostatnio: krwq
0

Oku**a nie spodziewałem się całego kodu, który na dodatek działa. Wielkie dzięki teraz przestudiuję kod i spróbuję to ogarnąć. Podziękowania.

I szybkie pytanko jakie zabiegi muszę wykonać aby dodać na przykład funkcje mnozenie?
Napisałem funkcje mnozenie:

Kopiuj
int mnozenie(stack s)
{
	int wynik = 0;
	while (!s.empty())
	{
		wynik*=s.top();
		s.pop();
	}
	return wynik;
} 

I jak wykonuje ASSIGN(mnozenie) to pisze, że funkcja zwraca 0 nieważne dla jakich argumentów, pewnie coś głupiego robię. Wielkie podziękowania za kod.

0

Ale jestem głupi jak dam wynik = 0 to przemnoży przez zero i da zero, dałem wynik = 1 i działa. Naprawdę jestem Ci bardzo wdzięczny.

Przerobię ten kod na moje potrzeby.

galleanv
  • Rejestracja:prawie 15 lat
  • Ostatnio:ponad 8 lat
0

Dodam, że można "zatrudnić" do tego np. Lua http://www.lua.org/

edytowany 1x, ostatnio: galleanv
0

No właśnie tak rozmyślałem nad tym. Muszę to jeszcze przemyśleć.

CC
  • Rejestracja:około 14 lat
  • Ostatnio:prawie 14 lat
  • Postów:58
0

co robią te dwie linie kodu?

Kopiuj
typedef int (*func)(stack);
#define ASSIGN(a) trans[#a] = a;
KR
  • Rejestracja:prawie 16 lat
  • Ostatnio:5 miesięcy
  • Postów:2514
0
Kopiuj
typedef int (*func)(stack);

definiuje typ o nazwie func, który jest wskaźnikiem do funkcji która zwraca int i w parametrze przyjmuje zmienną typu stack

Kopiuj
#define ASSIGN(a) trans[#a] = a;

definiuje makro o nazwie ASSIGN, które przyjmuje 1 parametr (o nazwie 'a'), makro te generuje linię kodu:
trans[#a] = a;
trans - mapa która mapuje string na funkcje, działa to np tak:
trans["nazwafunkcji"] = nazwafunkcji;

jak widac pierwsza część jest stringiem, a druga funkcją. zamiana nazwy na stringa odbywa się poprzez zastosowanie # w makrze (stringizing operator)


░█░█░█░█░█░█░█░█░█░█░█░
CC
  • Rejestracja:około 14 lat
  • Ostatnio:prawie 14 lat
  • Postów:58
0

Dzeki. Po co tylko tutaj typedef?

Kopiuj
typedef int (*func)(stack);
galleanv
Wskaźniki na funkcje wyglądają paskudnie i typedef utrudnia zrozumienie "jak to działa", ale kod wygląda ładniej xD
KR
  • Rejestracja:prawie 16 lat
  • Ostatnio:5 miesięcy
  • Postów:2514
0

dla uproszczenia pozniejszego zapisu typu funkcyjnego. duzo lepiej widac po prostu jak sa wszystkie typy zebrane kolo siebie. w map<string,func> jest to uzyte


░█░█░█░█░█░█░█░█░█░█░█░
edytowany 1x, ostatnio: krwq
CC
  • Rejestracja:około 14 lat
  • Ostatnio:prawie 14 lat
  • Postów:58
0

Niebardzo rozumiem. Wielokrotnie przeglądając kody zródłowe niektórych programów natrafiłem na taki zapis z tym typedef jeśli chodzi o wskaźniki na funkcje jak i struktury i nie bardzo wiedziałem jego sens.

KR
  • Rejestracja:prawie 16 lat
  • Ostatnio:5 miesięcy
  • Postów:2514
0

przyklad

Kopiuj
#include <stdio.h>

typedef int (*func)(int);

int przemnozprzezdwa(int a)
{
  return a*2;
}

int podzielprzezdwa(int a)
{
  return a/2;
}

int main()
{
  printf("podaj liczbe: ");
  int a;
  scanf("%d", &a);
  printf("chcesz pomnozyc czy podzielic przez 2 (0 - pomnozyc, 1 - podzielic)");
  func tab[] = {pomnozprzezdwa, podzielprzezdwa};
  int wybor;
  scanf("%d", &wybor);
  printf("wynik: %d", tab[wybor](a));
  return 0;
}

nie kompilowalem, ale koncepcyjnie jest dobrze

wyobraz sobie ze teraz takich funkcji masz 50, chcialoby Ci sie robic taka drabinke ifow?
teraz sam zapis:
func tab[]

wyglądałby chyba tak:
int (*tab[])()

albo tak:
int (*tab)

albo nie wiem jak :P


░█░█░█░█░█░█░█░█░█░█░█░
edytowany 2x, ostatnio: krwq
CC
  • Rejestracja:około 14 lat
  • Ostatnio:prawie 14 lat
  • Postów:58
0

Dzięki wielkie.Juz kojarzę idee tego.

0

Wie ktoś może jak z tego zrobić funkcje które będą przyjmowały stringi? Rozumiem, że wtedy również typ zwracany przez funkcję musi być std::string?
Gdy pozmieniałem wszystko na string, to się kompiluje, ale gdy wywołuje jakąś funkcję gdzie ma wypisać stringi podane na stack'u to nic nie wyświetla - ktoś ma jakiś pomysł?

KR
  • Rejestracja:prawie 16 lat
  • Ostatnio:5 miesięcy
  • Postów:2514
0

bo musisz jeszcze zrobic zeby jako tekst traktowane bylo wszystko co w cudzyslowiu jest. funkcja ktora przyjmuje stringa nie musi zwracac stringa. moze zwracac cokolwiek


░█░█░█░█░█░█░█░█░█░█░█░
0

Czyli w call_func muszę dodać znajdowanie "" i to co znajdzie pomiędzy "" to wrzucać jako parametr? Ale chyba nie mogę zrobić funkcji typu int, która przyjmuje stringi i używać w tym kodzie prawda? Dzięki za pomoc, przy okazji bardzo pomocny kod, propsy.

KR
  • Rejestracja:prawie 16 lat
  • Ostatnio:5 miesięcy
  • Postów:2514
0

Czyli w call_func muszę dodać znajdowanie "" i to co znajdzie pomiędzy "" to wrzucać jako parametr?
tak
Ale chyba nie mogę zrobić funkcji typu int, która przyjmuje stringi i używać w tym kodzie prawda?

możesz, z tym że musisz pamiętać o tej linijce i innych

Kopiuj
int ret = it->second(st);

jeśli chcesz parametry mieszane tj. np.: string i int jednocześnie to powinieneś zrobić klasę która przechowuje jeden z tych typów (przeciążyć operatory itp.) wszystko kwestia wyobraźni. generalnie: wszystko da się zrobić


░█░█░█░█░█░█░█░█░█░█░█░
0

Chciałbym po prostu aby ten stack to były stringi oraz żeby funkcje zwracały int'y. Więc typedef'y zmieniam z:

typedef std::stack<int> stack;
using std::cin;
using std::cout;
using std::string;
using std::endl;
typedef int (*func)(stack);
typedef std::map<string,func> transmap;

na:

typedef std::stackstd::string stack;
using std::cin;
using std::cout;
using std::string;
using std::endl;
typedef int (*func)(stack);
typedef std::map<string,func> transmap;

I to tyle + pozbywam się paru linijek w call_func i zmieniam obsługę żeby jako parametr dodawała na stack napis?

KR
wydaje mi sie ze powinno wystarczyc
0

Dzięki krwq, wszystko działa ładnie.

0

Siema, czy da się w jakiś sposób dodać do tych funkcji jeszcze jeden parametr, ale inny niż ten stos? Np int'a albo jakikolwiek inny typ?

KR
  • Rejestracja:prawie 16 lat
  • Ostatnio:5 miesięcy
  • Postów:2514
0

a kompilowałeś w ogóle ten program? wiesz po co ten stos tam jest? pytania retoryczne - jakbyś zrobił/wiedział którąkolwiek z tych rzeczy to byś wiedział, że ten stos przechowuje parametry o typie int i stos jest użyty po to, aby można było podać tych parametrów nieograniczoną ilość


░█░█░█░█░█░█░█░█░█░█░█░
0

Źle sformułowałem pytanie, czy mogę oprócz tego stosu int'ów wrzucać również jako np ostatni parametr zupełnie innego typu? Np string?

"jeśli chcesz parametry mieszane tj. np.: string i int jednocześnie to powinieneś zrobić klasę która przechowuje jeden z tych typów (przeciążyć operatory itp.) wszystko kwestia wyobraźni. generalnie: wszystko da się zrobić"

KR
  • Rejestracja:prawie 16 lat
  • Ostatnio:5 miesięcy
  • Postów:2514
0

w takiej wersji jak jest nie możesz, musisz powiedzieć programowi jak ma wszystko interpretować.
przeanalizuj sobie przykładowe wywołanie, które powinno działać tak jak Ty chcesz (XXX nic nie oznaczają, robie je po to, żeby było widać spacje):

XXXXXXX nazwafunkcji ( 123123 , "asdasdasdasd" , 22222 , "asdasdas" ) XXXXXX

czytasz tak:

  • rozdzielasz stringa na dwa: wszystko przed lewym nawiasem wszystko po lewym nawiasie (jeśli brak nawiasu -> błąd):
    A = XXXXXXX nazwafunkcji XXXXXXX
    B = XXXXXXXX 123123 , "asdasdasdasd" , 22222 , "asdasdas" ) XXXXXX
  • szukasz prawego nawiasu i wraz z nim wszystko co jest na prawo wywalasz (mozesz ewentualnie sprawdzic czy to na prawo to biale znaki):
    A = XXXXXXX nazwafunkcji XXXXXXX
    B = XXXXXXXX 123123 , "asdasdasdasd" , 22222 , "asdasdas" XXXXXX
  • w stringu A wywalasz białe znaki na lewo i prawo i sprawdzasz czy to co zostało jest prawidłową nazwą funkcji:
    A = XXXXXXXnazwafunkcjiXXXXXXX
  • stringa B zaczynasz parsować:
    idąc znak po znaku patrzysz czy pierwsze co napotkałeś to liczba czy "
    w zależności od tego co napotkałeś inaczej interpretujesz zawartość, do Twojej klasy wrzucasz dany parametr i wrzucasz go na stos i parsujesz dalej

░█░█░█░█░█░█░█░█░█░█░█░
0

Po pierwsze bardzo dziękuję za pomoc. Po drugie mi chodziło bardziej o coś takiego:
nazwa_funkcji(stack s,inna_zmienna_innego typu), po prostu abym mógł przekazać do funkcji inną zmienną już nie na zasadzie tego co wpisze użytkownik.

KR
  • Rejestracja:prawie 16 lat
  • Ostatnio:5 miesięcy
  • Postów:2514
0

to musisz zmienić tylko dwie linijki:

Kopiuj
typedef int (*func)(stack,int);
...
int ret = it->second(st,1234);

oraz wszystkie funkcje tak, żeby przyjmowały ten dodatkowy parametr


░█░█░█░█░█░█░█░█░█░█░█░
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)