Új hozzászólás Aktív témák
-
modder
aktív tag
válasz
BTminishop #2341 üzenetére
C# https://duckduckgo.com/?q=C%23
C++ https://duckduckgo.com/?q=C%2B%2B
gui toolkit: [link]
QT https://duckduckgo.com/?q=QT+UI
objektumorientált programozás
https://duckduckgo.com/?q=objektumorient%C3%A1lt+programoz%C3%A1sEgyébként windowsra C++-ból használhatod a windows native libraryt:
http://www.winprog.org/tutorial/simple_window.htmlLink javítva.
[ Módosította: Qru ]
-
modder
aktív tag
szerintem átrendezted a kódot ahhoz képest, ahogy a hibaüzenetek állapotában volt
Egyik dolog:
adatok uzenetek[uzszam]=elso();
[...]
adatok elso() { ... }az elso() egy darab elemet ad vissza, míg az uzenetek egy tömb.
Nagyobb gond, hogy ha az elso() még tömböt is adna vissza valamilyen csoda folytán, akkor sem inicializálhatnád így az uzenetek változót. Ha inicializálni akarnád, akkor:
adatok uzenetek[uzszam] = {adat1, adat2, adat3, adat_uzszam-1}Miért?
- Mert fordítási időben tudni kell a tömb méretét, ha inicializálni is akarod deklarálásnál!
- Mert nem lehet egész tömböket másolni az értékadás operátorral. Tömb másolása vagy memcopy() fv. vagy for-ciklussal lehetséges. Általában az utóbbit használjuk.Ha belegondolsz, hogy az adatok uzenetek[uzszam] egy sizeof(adatok)*uzszam hosszú memóriaterület, rájössz, hogy az értékadásnak egy ilyen hosszú memóriaterületet kéne átmásolni úgy, mint a memcpy() fv. Ez túl sok hibához vezetne valószínűleg ezért nincs benne.
Másik dolog:
adatok[] elso() { ... return uzenetek2; }sem működik. Nem tudom tanultad-e hogyan működik a függvényhívás és mi a stack. Amikor hívsz egy függvényt, akkor a függvény visszatérési típusának megfelelő méretű memória területet szabadon hagy a stacken fv hívása előtt, visszatéréskor ide másolja a visszatérési értéket. Ehhez tudni kell a visszatérési érték pontos hosszát.
Ezért nem térhetsz vissza tömbbel, mert annak nem tudod az egzakt hosszát futásidőben.Visszatérhetnél pointer típussal:
adatok* elso() {... return &uzenetek2; }Ezzel már csak az a baj, hogy a mód, ahogyan uzenetek2-t deklaráltad, az a stacken jött létre, és megszűnik létezni, miután a függvény visszatért, ezért ha visszatérés után hivatkoznál rá, memóriaszemétre mutatna. (new-val lehetne a heap-en foglalni, de most ez nem kell)
Megoldás:
adatok uzenetek[uzszam];
elso(uzenetek, uzszam);
void elso(adatok[] uzenetek, uzszam) { beolvasol az uzenetekbe }Amint látod a függvényargumentumokat lehet visszatérési értékek tárolására is használni. Tömb esetében ez ilyen egyszerű. Ha nem tömböt használsz, akkor referenciát kell átadni:
void valami(adat& uzenet) { uzenet = createUzenet(); }Ez teljesen elfogadott, hogy lefoglalod a memóriaterületet a tömbnek először, és ezt adod át a függvénynek, hogy feltöltse adatokkal.
-
modder
aktív tag
válasz
pvt.peter #2160 üzenetére
az eredeti példát kiegészítettem másoló konstruktorral:
http://pastebin.com/ryGfdzyzAmi egyébként ilyen egyszerű esetben nem szükséges, mert a nyelv csak byteról bytra lemásolja az eredeti objektumot inicializálásnál, tehát:
// copy konstruktor hívódik, amikor egy objektumot egy másik objektummal inicializálsz
Dog pajti2 = *makeDog();A nyelv átmásolja az egyenlőség jel jobb oldalán álló objektumot byteról byte-ra, vagy ha létezik, akkor a másoló konstruktor segítségével az egyenlőségjel bal oldalán lévő objektumba. Másoló konstruktor csak inicializálásnál hívódik meg, ami az új függvény deklarációja és egyből értékadás.
bővebben:
http://www.fredosaurus.com/notes-cpp/oop-condestructors/copyconstructors.html
(van példa kód, hogy mikor hívódik meg) -
modder
aktív tag
válasz
Dave-11 #2157 üzenetére
ja, hát igen, ott nem hívsz delete-et, szóval értelemszerűen nem szabadítod fel, tehát ja, memória szivárgás. azért hozzá kell tenni, hogy ahogy meghal a processz, nyilván felszabadul minden lefoglalt memória, de ha egy ilyen kódrészlet valahol be van ágyazva a programban, akkor igen, ez pont az: memóriaszivárgás
Szerk:
Ja várjál, most látom, hogy mit csinálsz. Igen, ez mindenképpen rossz, mert amikor a Pajtit létrehozod, akkor a Dog másoló konstruktora hívódik meg, szóval lesz két változód. Egy a heap-en, amit a makeDog()-ban csináltál, egy pedig a stack-en, a Pajti. És már fel sem tudod szabadítani a heap-en foglalt memóriaterületet, mert elveszetetted, a hozzá tartozó memóriacímet, amikor visszatértél a függvényből. A címet, amit a lenti esetben a Pajti* változód tárol, azaz a pointered. -
modder
aktív tag
Azt akartam mondani, hogy alapvető, hogy két futó szál között nem feltételezed, hogy A szálban futó kódrészlet előbb fut le, mint a B szálban futó, akármilyen kontextusban, hanem párhuzamosan futnak.
Az async szervizes példádat pedig nem értem, talán ha megfogalmaznád, úgy egyszerűbb lenne.
-
modder
aktív tag
válasz
Zoli133 #2144 üzenetére
én is csináltam ilyet, hogy az egyik szál loggolt fájlba, a másik szál pedig gyártotta a log üzeneteket, de én az üzeneteket blocking queue-ba tettem, így ha a loggoló szál lassabban loggol (a queue megtelik), a másik szál is lassabban fut, mert a lista blokkol. Bármelyik szál belassulhat, akkor is helyes eredményt fog produkálni.
Ha te kitesztelted, hogy 2mp-enként kell takarítani ahhoz, hogy a többi szál ne terhelje túl a heap-et, de ráteszed egy másik gépre, környezetre, ahol gyorsabban termelődnek az objektumaid, akkor már egyből módosítani kell a takarító szálat is, ez így nem fasza. Legalábbis ez jött le eddig, de korrigálj, ha tévedekszerk.: bár én javaban csináltam de a blocking queue koncepciója C++-ban is létezik
-
modder
aktív tag
válasz
Zoli133 #2140 üzenetére
Mit értesz szinkronizáció alatt?
Mondjuk nem írtam még több szálú alkalmazást C++-ban, csak Javában, de én úgy tudom, hogy egy több szálú alkalmazásnál alapvető, hogy nem szabad feltételezni semmiféle sorrendiséget szálak között. Valami olyasmit tudok elképzelni, hogy éheztetés lép föl azért, mert valamelyik szál olyan lassan hajtódik végre, hogy az OS ütemezője lelépteti, mielőtt befejezné a feladatát. De ha alapvetően azt nézzük, hogy minden szál egyenlő eséllyel egyenletes eloszlásban kap futási időt, akkor nem értem, hol lehet a probléma. -
modder
aktív tag
válasz
WonderCSabo #2104 üzenetére
azt magyarul úgy mondják, hogy kazalmemória
-
-
modder
aktív tag
-
modder
aktív tag
válasz
mgoogyi #2079 üzenetére
Hali,
Azt hiszem igazad van, a "A b = B();" tényleg az A copy konstruktorát fogja meghívni, tehát ez egy A objektum lesz. Akkor viszont pvt.péter felvetésére mégis csak jogos, hogy a dinamikus kötés csak pointeren keresztül lehetséges C++-ban. De nem a stack vs. heap miatt, hanem a pointer miatt.
Megnéztem az egyik régi házimat, ahol nem lehetett new operátort használni, és így oldottam meg:
Henger henger;
Asztal asztal;
Object* objects_[ MAX_OBJECTS ] = {&henger,&asztal};Ezt csak azért írom, mert a new operátort direkt el akartam kerülni az egész példámban, hogy látszódjon, anélkül is lehet dinamikus kötést alkalmazni.
Köszi a tisztázást!
-
modder
aktív tag
válasz
pvt.peter #2074 üzenetére
Szia, Karmának igaza van mindkét esetben. A heap vagy stack nem befolyásol semmit a virtuális függvények terén, a new operátor pedig tényleg hibát dobna az első esetben. Javítva:
A b = B();
b.valami();
// most vonatkoztassunk el attól, hogy ugyanaz a változónév
B b = B();
b.valami()(már régen c++-tam)
-
modder
aktív tag
Hali,
Csinálsz egy 10 elemű segédtömböt, amibe a 10, tag1 attribútum szerinti legkisebb értéket fogod tárolni. Ez a segédtömb tag1 szerinti NÖVEKVŐ sorrendben fogja tartalmazni az Adatszerk típusú adatokat.
Végigiterálsz az eredeti tömbön, és minden egyes elemére megnézed, hogy a tag1 attribútuma kisebb-e, mint a legnagyobb tag1 attribútum a segédtömbödben. Ha igen, akkor az addigi 10 legkisebb közé be fog kerülni, err szolgál a pushMin() metódus.struct Adatszerk {
int tag1;
int tag2;
}
// legyen egy listád az adatszerkezettel
Adatszerk adatok[] = { Adatszerk(1,1), Adatszerk(1,2), ... }
int adatokSize = 50 // vagy akármennyi
Adatszerk[] legkisebb10 = Adatszerk[10];
initLegkisebb10( legkisebb10 ); // mindegyiket feltöltöd legalább akkora értékkel, amekkora maximum értéke lehet tag1-nek
for( int i = 0; i < adatokSize; i++ ){
if( adatok[i].tag1 < legkisebb10[9].tag1 ) {
pushMin( legkisebb10, adatok[i] );
}
}
// ezzel a függvénnyel növekvő sorrendbe szúrjuk be 'legkisebb10'-be a legkisebb tag1 attribútum szerinti
// adatokat
void pushMin( Adatszerk[] legkisebb10, Adatszerk adat ) {
int i = 0;
for ( i = 0; i < 10 ; i++ ) {
if ( adat.tag1 < legkisebb10[i].tag1 ) {
break;
}
}
// a legkisebb10 tömbben az i. helyen volt az elem, ami már nagyobb volt 'adat'-nál
// ezért oda tesszük be az 'adat'-ot, és a maradékot hátra toljuk a tömbben
// fontos, hogy a for-ciklus a tömb hátulja felől menjen i-ig
for( int k = 9 ; k > i ; k-- ) {
legkisebb10[k] = legkisebb10[k-1];
}
// i-edig helyre beszúrjuk az új 'adat'-ot
legkisebb[i] = adat;
}Amit WonderCSabo is említett, ha nem olyan nagy a listád, szóval lemásolhatod, akkor lehet, hogy egyszerűbb először rendezve lemásolni, majd az első 10-et kiírni
-
modder
aktív tag
-
modder
aktív tag
Ez így elég absztrakt. Ez az iterátor akar lenni? Iterátort azért használnak, hogy elrejtsék a tároló adatstruktúra sajátosságait. például ha tároló struktúra egy bináris fa, azt nem tudod tömbként visszaadni anélkül, hogy le ne másolnád az egész struktúrát, aminek nem sok értelme van. Ezért csinálnak iterátort.
-
modder
aktív tag
válasz
pvt.peter #2057 üzenetére
Hali. Polimorfizmus (többalakúság, ugyanolyan ős típusú objektumok másképpen viselkedhetnek). Amikor több osztályod van, ami ugyanazokat a tulajdonságokat (metódusokat) definiálja, ezért közös ősből származik, de mégis minden osztály egy kicsit másképpen viselkedik, vagyis kicsit más az implementációjuk, viszont az interfészük (amit az osztály használója lát) megegyezik.
Most hirtelen a Java JDBC API jut eszembe:
//Create the connection using the static getConnection method
Connection con = DriverManager.getConnection (connectionURL);
//Create a Statement class to execute the SQL statement
Statement stmt = con.createStatement();Itt a DriverManager egy factory osztály, ami olyan Connection példányt ad vissza, ami adatbázis specifikus a szerint, hogy milyen adatbázis típus szerepel a connection URL-ben. A Connection csak egy interfész, minden konkrét adatbázis JDBC driver a sajátját specifikálja, és a konkrét, con változóhoz kötött példány lehet, hogy mondjuk MysqlConnection típus lesz. Itt a lényeg az, hogy a MysqlConnection a Connection-ből származik, és felülírja a Connection metódusait.
Ami fontos megjegyezni futás időben fog eldőlni, hogy melyik metódus fog meghívódni, mert fordításkor lehetetlen eldönteni a fenti kódrészletből, hogy a con változó konkrétan milyen osztály lesz.
C++-ban explicite ki kell írnod a virtual kulcsszót a függvény elé. Ha nem teszed ki, akkor is felülírhatod a metódust, de nem biztos, hogy azt az eredményt fogod kapni, amit vársz. Például
class A {
public:
void valami() { std::cout << "A"; }
virtual void virt() { std::cout << "A"; }
}
class B : A {
public:
void valami() { std::cout << "B"; }
void virt() { std::cout << "B"; }
}A b = new B();
b.valami(); // "A" fog kiíródni, mert valami() nem virtuális, tehát a változó "statikus" típusa alapján dől el, hogy melyik metódus fog meghívódni. A statikus típus pedig "A"B b = new B();
b.valami(); // itt a statikus típus "B", tehát "B" fog kiírodóni.Ezzel szemben
A b = new B();
b.virt(); // itt "B" fog kiíródni azért, mert a virt() függvény virtuális. futás időben a virtuális függvény táblából a program megnézi, hogy melyik konkrét függvény hívódjon meg. Mindezt a b változó futásidejű (dinamikus) típusa alapján, ami itt "B"Heterogén kollekciókban szokták hasznát venni, amikor (mint az első példában) egy közöt ős van, ami szolgáltatja az interfészt, de többféle implementációt tárolsz egy tömbben vagy listában. Amikor végigiterálsz rajtuk, hogy meghívd mindegyiken valamelyik metódusát, nem kell foglalkoznod a konkrét típussal, ami nagyban leegyszerűsíti a programozó munkáját. Ez annyira az általános elvárt működés, hogy Javában minden metódus virtuális. Ha nem akarod, hogy valamelyiket felül lehessen írni, akkor a final kulcsszót kell elé tenni.
Amikor egy osztályt kiterjeszthetőségre tervezel, ki kell választanod azokat a metódusait, amiket felül lehet majd írni, és virtuálissá teszed őket. Ezzel elég körültekintőnek kell lenned, mert egy felülírt virtuális metódus a származtatott osztályban el is ronthatja az alap működést.
-
modder
aktív tag
válasz
Ton-ton #2050 üzenetére
nekem az Eclipse jól bevált C++-ra. Automake meg mindenféle van benne, cross-compilingra is biztosan be lehet lőni. Lehet, hogy nem tökéletes, de elég jól működik benne a kódkiegészítés, meg minden cucc. A Qt developert még nem próbáltam ki sima C++ alkalmazás készítésére, egyszer volt alkalmam vele találkozni, és nagyon tetszett az egyszerűsége és gyorsasága.
-
modder
aktív tag
válasz
Spam123 #2042 üzenetére
ha unsigned shortban vagy intben tárolod a számokat, akkor a biteket nem tömbelemekként éred el, hanem bitszintű operátorokat kell használnod. maszkolnod, shiftelned kell &, >> és << operátorokkal.
Talán kicsit bonyolultabb, de szerintem szebb megoldás, ha már alacsony szintű működést kell szimulálni. Meg kevesebb helyet is foglalsz így, mert egy boolean változó a memóriában szintén lefoglal vagy 2 byteot, ami összesen 32 byte. -
modder
aktív tag
Hát itt alapvetően 3 lehetőséged van szerintem.
1) OpenGL/DirectX. Én csak az előbbihez értek. Kirajzolni egyszerű 2D objektumokat nem túl nehéz. Az ablakkezelőt GLUT-nak hívják, és setuppolni windows alatt egy freeglut környezetet nem olyan egyértelmű, de vannak tutorialok neten. Kirajzolni egyszerű alakzatokat viszont nem nehéz pl.: http://www.gamedev.net/page/resources/_/technical/opengl/basics-of-glut-r1680 . Ja, GLUT, OpenGL elvileg platformfüggetlen.2) GnuPlot library http://www.gnuplot.info/download.html . Ez csak egy gyors ötlet volt, elvileg ezt is fel tudod tenni windowsra, ha van egy MinGW környezeted. Linux alatt valószínűleg ez is csak egy yum install gnuplot-dev vagy aptitude install gnuplot-dev.
3) valamilyen grafikus könyvtárat használsz, mint Qt, GTK+ vagy wxWidgets. Sajnos nem tudok róla nyilatkozni, hogy melyiket milyen egyszerű használni, de elvileg mind a három multiplatform. Mindegyikben van canvas-szerű komponens, amivel 2D ábrákat gyárthatsz. Most így hirtelen az a megérzésem, hogy wxWidgets talán pont megfelelne.
4 (bónusz) Ha csak ábra kell, generálj képet pl. ezzel: http://cairographics.org/samples/.
vagy generálj HTML-ben egy SVG képet, majd nyisd meg böngészőben.Mivel én már használtam OpenGL-t, ezért én azt választanám. Ha kell valami extra, mondjuk külfönféle görbéket akarsz illeszteni a mintavételi pontokra, arra fog kelleni valamilyen másik library, de alapvetően megjelenítésre az OpenGL még mindig megfelelő.
-
modder
aktív tag
válasz
Azazel999 #2000 üzenetére
Az király ha sikerült. Tegnap én sem értettem az algoritmusodat, aztán lejátszottam papíron úgy, hogy a keresett elem (a vágási pont) tetszőleges a fában, aztán rekonstruáltam az új fát, és jó lett.
Viszont nem jöttem rá, hogy beszúrásnál mi a teendő, mert ha ez egy szimpla bináris fa, és keresünk egy nem létező elemet, akkor elérünk az egyik levélbe. akkor melyik lesz a vágási pont? Kipróbáltam több verziót: a vágási pont a létező levél, vagy a vágási pont az új elem, vagy a vágási pont a levél előtti elem, de egyik esetben sem kaptam kiegyensúlyozott fát. Ezt a lépést leírnád?
Amúgy meg rekurzióval tényleg egyszerűbb. az ugye csak egy Depth First Search, ahol minden lépés után vagy B vagy J tömbbe teszed a részfákat, a végén pedig mikor visszatérsz a keresésből építesz egy új fát a B és J elemekből. De általában "hatékonyabb" a nem rekurzív megoldás: erőforráskímélőbb, hiszen nem kell állandóan függvényt hívni.
Amúgy meg erről eszembe jutott, az 1. féléves C nagyházim. Egy logikai kifejezés kiértékelő program tetszőleges logikai kifejezést megadva, épít belőle egy fát (amit ma Abstract Syntax Tree-nek mondanék, mert az jó hangzatos), majd bejárja és közben kiértékeli a kifejezést. Miután működött, három napomba tellett, mire kijavítottam a pointerezést, és a Valgrind végre nem mutatott memória szivárgást
szerk: azt akartam kihozni belőle, hogy jó, hogy meg tudtad oldani egyedül, mert mire kiszeneded magadból a megoldást, sokat megtanulsz
-
-
modder
aktív tag
válasz
WonderCSabo #1722 üzenetére
elég sok "új" feature van, amiket érdemes lenne átgyakorolni, hogy ne ragadjunk le a nyelv 10 évvel ezelőtti állapotánál. persze leginkább azoknak érdemes, akik aktívan használják.
-
modder
aktív tag
válasz
dabadab #1717 üzenetére
szerencsére már standard C++-ban is van
http://en.wikipedia.org/wiki/C%2B%2B11#Range-based_for-loop -
modder
aktív tag
válasz
Des1gnR #1480 üzenetére
Miért próbálod meg egy char* tömbbe átkopizni a buffered egyik sorát?
egyébként az valszeg működik. ami nem működik az a while feltételed. a while-nak bennmaradási feltételre van szüksége. csinálja amíg igazwhile(a(i)=='A' || a(i)=='F'); itt már kapásból ki fog lépni, mert ez hamis lesz.
próbáld ki ezt: while(a(i)!='A' && a(i)!='F');
Ha pedig meg akarod könnyíteni az életed, akkor:
int n,i=0;
cout<<"Melyik sorban?"<<endl;
cin>>n;
i = olv_buffer2[n].length() - 1;
while( olv_buffer2[n].at(i) != 'A' && olv_buffer2[n].at(i) != 'F' ){
// hatulrol keresunk, mert az utcso karakter, de akar meg lehet whitespace is utana
i--;
}
cout<<olv_buffer2[n]<<endl;
cout<<olv_buffer2[n].at(i)<<endl; -
modder
aktív tag
válasz
Des1gnR #1478 üzenetére
Hali
Ez mi?
do{
i++;
}while(olv_buffer[n,i]=='A' || olv_buffer[n,i]=='F');
cout << "A karaket: "<<olv_buffer[n,i]<<endl;Nem emlékszem, hogy valaha is ilyen módon kellett volna címezni tömböt C-ben.
Plusz miért lenne az olv_buffer egy többdimenziós tömb?Amúgy ahogy ezt előbb is írták kb. ilyesmire: olv_buffer[ olv_buffer.length()-1 ] -re vizsgálj.
-
modder
aktív tag
Ez egy alapmű. Nem csak a nyelv szerkezetét írja le, szintaxist, hanem C-stílusú kódolásra is tanít. Eléggé olvasmányos amúgy.
http://www.friweb.hu/kr-c/ -
modder
aktív tag
válasz
Blaise7 #1146 üzenetére
Helló, természetesen igen, de nem pontosan abban a formában, ahogy leírtad.
Heterogén kollekciónak hívják, és a lényeg, hogy bármilyen osztály objektumait beleteheted, amik a kollekció típusának leszármazottai.
Legyen
class Os {};
class Gyerek: public Os {};
Os* tomb[10];Ahogy látod Os* (azaz Os-re mutato pointer) típusokat tárolok a tombben, és ha hozzá akarok adni egy elemet, akkor a címét kell hozzáadni:
Os[0] = new Gyerek;
vagyGyerek gyerek;
Os[1] = &gyerek;Ez az egyetlen módja, hogy egy tömbben gyerek osztályokat is tudj tárolni. Az oka az, hogy a két osztály memóriatérképe eltérő. Amíg Os-ben lehet pl. 1db int, addig Gyerekben legyen 10db float, plusz az Os 1db intje.
Ha Os tipusu tombot inicializálsz, akkor a memóriában (a te esetedben) 10db Os osztály méretű hely foglalódna a memóriában, ami a fentiekből adódóan 10 * 1db int-et jelentene.
Erre a helyre nyilván nem férne bele a 10 * (10db float + 1db int), ami a gyerek osztály mérete.Az egyetlen módszer, hogy pointer tömböt inicializálsz, mert egy pointer egy int méretű, és bármilyen memóriacímre mutathat.
Remélem érthető voltam, keress rá a C++ heterogén kollekcióra.
-
modder
aktív tag
válasz
Barett 50cal #1138 üzenetére
int eldontfunkcio(int* t){
static int count = 0;
if( count >= 10 )
return 0;
if( *t > 50 )
std::cout << count << " ";
count++;
return 1;
} -
modder
aktív tag
válasz
Barett 50cal #1135 üzenetére
nem int-ként, hanem szövegként kéred be (char[]), és utána átalakítod intté, miután leellenőrizted, hogy nem tartalmaz-e karaktert.
Új hozzászólás Aktív témák
Hirdetés
● ha kódot szúrsz be, használd a PROGRAMKÓD formázási funkciót!
- Csere-Beszámítás! AMD Számítógép PC Játékra! R5 5500 / RX 5700XT / 32GB DDR4 / 256SSD+1TB HDD
- PS3 Játékok 1500Ft/db - RÉSZLETEK A LEÍRÁSBAN
- ÁRGARANCIA!Épített KomPhone Ryzen 7 9800X3D 64GB RAM RTX 5080 16GB GAMER PC termékbeszámítással
- BESZÁMÍTÁS! MSI B460M i5 10400F 16GB DDR4 512GB SSD RX 6650XT 8GB Cooler Master MB600L Chieftec 600W
- Új! Targus - USB-C Dual HDMI 4K HUB - 2 HDMI-vel. Saját töltő nélkül 2 monitorral (120Hz)
Állásajánlatok
Cég: PCMENTOR SZERVIZ KFT.
Város: Budapest
Cég: Promenade Publishing House Kft.
Város: Budapest